When you generate a new Angular component, you get a test like this:


import { ComponentFixture, TestBed } from '@angular/core/testing';

import { SkrunchWiggleComponent } from './skrunch-wiggle.component';

describe('SkrunchWiggleComponent', () => {
  let component: SkrunchWiggleComponent;
  let fixture: ComponentFixture<SkrunchWiggleComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [SkrunchWiggleComponent]
    })
      .compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(SkrunchWiggleComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

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

When we call fixture.detectChanges() in the beforeEach, that causes Angular to call ngOnInit. If you need to set up mock services that will be called in the ngOnInit method of the component, there is no good place to put those mocks in this test. If you put it inside the test body, it’s too late – ngOnInit has already been called. If you put it in beforeEach, it applies to all tests. You could call ngOnInit directly in your test, but then it is invoked multiple times, which is not a good imitation of what happens in production. Depending on what you’re doing in the init method, calling twice could either cause or mask issues that don’t happen when it is called once.

In general, we avoid directly calling any component lifecycle methods so that our tests are more faithful to what really happens in production.

initialize Fixture

To avoid directly calling ngOnInit, you can postpone the first call to fixture.detectChanges() until after your mocks are set up. We use the “initializeFixture” pattern for this.

  function initializeFixture(options: { itemId?: number } = {}) {
  const fixture = TestBed.createComponent(SkrunchWiggleComponent);
  const component = fixture.componentInstance;
  component.itemId = options.itemId || 1;
  const element = fixture.nativeElement as HTMLElement;
  fixture.detectChanges();
  return { component, fixture, element };
}

Some observations about initializeFixture:

Use initializeFixture like so:


it('styles the title differently when this item is edited', () => {
  wiggleService.getDirectionOfRotation.and.returnValue('CLOCKWISE');
  const { element } = initializeFixture({ itemId });
  expect(element.textContent).toContain('Skrunch Wiggle Clockwise');
});

The example above deals with the most common lifecycle hook to call in test: ngOnInit.

ng On Changes

If you need to test ngOnChanges, a natural guess would be to change an input with component.myInput = 'new value' and you would expect ngOnChanges to be called. But the test bed does not call it automatically like that.

To cause the test bed to call ngOnChanges, you would need an example parent component. When you change the value on the parent, it passes it to the child (the test subject) and Angular calls ngOnChanges for you. Here is an example parent component which you could write in your test file.

@Component({
  selector: 'test-subject',
  template: `
    <app-skrunch-wiggle [itemId]="itemId">
    </app-skrunch-wiggle>
  `
})
class ExampleParent {
  @Input() itemId!: number;
}

In your test, you add the example parent in the declarations block for the TestBed.configureTestingModule()

beforeEach(async () => {
  await TestBed.configureTestingModule({
    declarations: [ExampleParent, SkrunchWiggleComponent],
  }).compileComponents();
});

When you initialize the fixture, use the example parent:

const fixture = TestBed.createComponent(ExampleParent);

Now, when you change the ExampleParent component’s itemId input, Angular calls the ngOnChanges in the test subject (child). This lets you execute the ngOnChanges in test organically.

Overall, this ExampleParent approach lets you test what happens to the child if the parent does one thing or another to it. “If a parent component did X to you, how would you behave?”

Mocking components

Another pattern that is similar to the ExampleParent is mocking a component. This is useful if the component has complex dependencies that you don’t need as part of your test, or if it has some runtime behavior that you don’t want as part of your test. You can create a component in the spec file that has the same selector as the component you want to mock. In the declarations section of your TestBed setup, use the fake component instead of the real one.

Instead of:

beforeEach(async () => {
  await TestBed.configureTestingModule({
    declarations: [SkrunchWiggleComponent, WiggleIconComponent],
  }).compileComponents();
})

use a fake component

@Component({
  selector: 'app-wiggle-icon',
  template: ''
})
class FakeWiggleIconComponent {
  @Input() direction!: string;
}

describe('ScrunchWiggleComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      // replace the real icon with the fake one:
      declarations: [SkrunchWiggleComponent, FakeWiggleIconComponent],
    }).compileComponents();
  })
})

Now, when you render the SkrunchWiggle component, it will render the fake wiggle icon component instead of the real thing. You can assert on the inputs to the fake wiggle like you would have asserted on the inputs to the real component.

Asserting on child component inputs

When you have a fake child, it is sometimes helpful to assert on the inputs passed to the child. You can do that like so:

it('passes the direction to the wiggle icon', ()=> {
  wiggleService.getDirectionOfRotation.and.returnValue('COUNTERCLOCKWISE');
  const { fixture } = initializeFixture({ itemId });
  const icon = fixture.debugElement.query(By.directive(FakeWiggleIconComponent)).injector.get(FakeWiggleIconComponent);
  expect(icon.direction).toEqual('COUNTERCLOCKWISE');
})