Angular 4 + Jasmine Unit Tests: Reasons why fixture.detectChanges() may not work upon change of @Input() variables

emok032 picture emok032 · Aug 11, 2017 · Viewed 7.4k times · Source

Been running into this one issue and scouring the internet about fixture.detectChanges() where it does not recognize changes in @Input() when explicitly inserting mock data. There are tons of threads and docs that describe the setup but not necessarily why it would cause all of my tests to break.

Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.

Removing fixture.detectChanges() seems to "resolve" this error. But now insertion of any new mock data (per spec) are not detected.

Example:

TestComponent.ts

import { TestComponent } from './test-component';
import { TestComponentModule } from './test-component.module';
import { Data } from './interfaces/data';

export class TestComponent {
    @Input() data: Data;

    displayData(){ 
        let firstName = data.first;
        let lastName = data.last;
        let fullName = firstName + ' ' + lastName;
        return fullName;
    };
}

TestComponent.spec.ts

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TestComponent } from './test-component';
import { TestComponentModule } from './test-component.module';

class DataMock {
    data: Data = getDataMock({
        first: 'Roger',
        last: 'Moore'
    });
};

describe('TestComponent', () => {
    'use strict';
    let testComponent: TestComponent;
    let fixture: ComponentFixture<TestComponent>;

    beforeEach(async() => {
        TestBed.configureTestingModule({
        imports: [ TestComponentModule ]
        }).compileComponents();
        fixture = TestBed.createComponent(TestComponent);
        testComponent = fixture.componentInstance;
        fixture.detectChanges();
    });

    it('should render the app', () => {
        expect(TestComponent).toBeDefined();
    });

    describe('displayData()', () => {
        let dataMock = new DataMock;
        beforeEach(() => {
            testComponent.data = dataMock.data;
        });

        it('should return fullName', () => {
            expect(TestComponent.displayData()).toBe('Roger Moore');
        });
    });
});
  • Originally, class dataMock was defined as const variable - in which case fixture.detectChanges() breaks all of the tests it's applied to.
  • Now, dataMock is a class with the mock Input() data and fixture.detectChanges() seems to work again

So, why is instantiating class dataMock before each spec necessary for the fixture.detectChanges() to work? Is this the reason?

Answer

purezen picture purezen · Jun 27, 2018

You must create the fixture after compileComponents is executed.

beforeEach(async() => {
    TestBed.configureTestingModule({
        imports: [ TestComponentModule ]
    }).compileComponents();
});

beforeEach(() => {
    fixture = TestBed.createComponent(TestComponent);
    testComponent = fixture.componentInstance;
    fixture.detectChanges();
});