Files
ISA-Frontend/docs/guidelines/testing.md
Lorenz Hilpert d38fed297d Add input controls and checkbox component
Introduced a new input controls library with a checkbox component.

-  **Feature**: Added input controls library with checkbox component
- 🎨 **Style**: Updated checkbox component styles and structure
- 🧪 **Test**: Added unit tests for checkbox and empty state components
- 🛠️ **Refactor**: Improved checkbox component code and removed unused styles
- 📚 **Docs**: Updated commit message guidelines in VSCode settings
2025-03-31 12:29:22 +02:00

4.5 KiB

Testing Guidelines

Unit Testing Requirements

  • Test files should end with .spec.ts
  • Use Spectator for Component, Directive and Service tests
  • Use Jest as the test runner
  • Follow the Arrange-Act-Assert (AAA) pattern in tests
  • Mock external dependencies to isolate the unit under test
  • Mock child components to ensure true unit testing isolation

Best Practices

Component Testing

  • Use createComponentFactory for standalone components
  • Use createHostFactory when testing components with templates
  • Mock child components using ng-mocks
  • Test component inputs, outputs, and lifecycle hooks
  • Verify DOM rendering and component behavior separately

Mocking Child Components

Always mock child components to:

  • Isolate the component under test
  • Prevent unintended side effects
  • Reduce test complexity
  • Improve test performance
import { MockComponent } from 'ng-mocks';
import { ChildComponent } from './child.component';

describe('ParentComponent', () => {
  const createComponent = createComponentFactory({
    component: ParentComponent,
    declarations: [MockComponent(ChildComponent)],
  });
});

Example Test Structures

Basic Component Test

import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { MyComponent } from './my-component.component';

describe('MyComponent', () => {
  let spectator: Spectator<MyComponent>;
  const createComponent = createComponentFactory(MyComponent);

  beforeEach(() => {
    spectator = createComponent();
  });

  it('should create', () => {
    expect(spectator.component).toBeTruthy();
  });

  it('should handle action correctly', () => {
    // Arrange
    spectator.setInput('inputProp', 'testValue');

    // Act
    spectator.click('button');

    // Assert
    expect(spectator.component.outputProp).toBe('expectedValue');
  });
});

Host Component Test with Child Components

import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';
import { ParentComponent } from './parent.component';
import { ChildComponent } from './child.component';
import { MockComponent } from 'ng-mocks';

describe('ParentComponent', () => {
  let spectator: SpectatorHost<ParentComponent>;

  const createHost = createHostFactory({
    component: ParentComponent,
    declarations: [MockComponent(ChildComponent)],
    template: `
      <app-parent 
        [input]="inputValue" 
        (output)="handleOutput($event)">
      </app-parent>`,
  });

  beforeEach(() => {
    spectator = createHost(undefined, {
      hostProps: {
        inputValue: 'test',
        handleOutput: jest.fn(),
      },
    });
  });

  it('should pass input to child component', () => {
    // Arrange
    const childComponent = spectator.query(ChildComponent);

    // Assert
    expect(childComponent.input).toBe('test');
  });
});

Testing Events and Outputs

it('should emit when button is clicked', () => {
  // Arrange
  const outputSpy = jest.fn();
  spectator.component.outputEvent.subscribe(outputSpy);

  // Act
  spectator.click('button');

  // Assert
  expect(outputSpy).toHaveBeenCalledWith(expectedValue);
});

Common Patterns

Query Elements

// By CSS selector
const element = spectator.query('.class-name');

// By directive/component
const child = spectator.query(ChildComponent);

// Multiple elements
const elements = spectator.queryAll('.item');

Trigger Events

// Click events
spectator.click('.button');
spectator.click(buttonElement);

// Input events
spectator.typeInElement('value', 'input');

// Custom events
spectator.triggerEventHandler(MyComponent, 'eventName', eventValue);

Test Async Operations

it('should handle async operations', async () => {
  // Arrange
  const response = { data: 'test' };
  service.getData.mockResolvedValue(response);

  // Act
  await spectator.component.loadData();

  // Assert
  expect(spectator.component.data).toEqual(response);
});

Tips and Tricks

  1. Debugging Tests

    • Use spectator.debug() to log the current DOM state
    • Use console.log sparingly and remove before committing
    • Set breakpoints in your IDE for step-by-step debugging
  2. Common Pitfalls

    • Don't test implementation details
    • Avoid testing third-party libraries
    • Don't test multiple concerns in a single test
    • Remember to clean up subscriptions
  3. Performance

    • Mock heavy dependencies
    • Keep test setup minimal
    • Use beforeAll for expensive operations shared across tests