Re-run Angular OnInit lifecycle in Unit test

Problem

Our problem is to be able to re-run our component's initialization lifecycles, specifically the OnInit lifecycle when writing the unit tests.

In most cases, our components will behave differently base on some conditions.

For example, let's say we want to decide to display a login page or home page, so we will need to check if a user is already logged in or not and this check should be happening during the initialization.

Solution

When we are using Angular CLI to build our components, we will use ng generate component MyComponent, and it will generate our component with some boilerplate unit test.

The unit test file is like this

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

import {MyComponent} from './my.component';

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

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

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

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

Here, the fixture.detectChanges() is the method which will trigger OnInit in our component.

So now that we know this, we already have the solution!

Let's say our component has @input() which will tell it the user authorization status.

@Component({
    selector: 'app-my-component',
    template: `
        <ng-container *ngIf="isLoggedIn else notLoggedInTemplate">
            <app-home></app-home>
        </ng-container>
        <ng-template #notLoggedInTemplate>
            <app-authorization></app-authorization>
        </ng-template>
    `
})
export class MyComponent implements OnInit {
    @Input() isLoggedIn: boolean;

    ngOnInit(): void {
        if (this.isLoggedIn)
            this.doSomethingBaseOnLogIn();
    }

}

So, base on this component script, we need to create our component two times (inside unit test) and pass in the isLoggedIn field and then run fixture.detectChanges() to be able to test if the component behaves as expected.

Our unit test should be like this

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

    beforeEach(async () => {
        await TestBed.configureTestingModule({
            declarations: [
                MyComponent,
                AuthorizationComponent,
                HomeComponent
            ]
        }).compileComponents();
    });

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

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

    describe('Behave correctly base on "isLoggedIn" status', () => {
        it('should display login component if not logged in', () => {
            fixture = TestBed.createComponent(SearchFormComponent);
            component = fixture.componentInstance;
            component.isLoggedIn = false;
            fixture.detectChanges();

            const myComponent = fixture.debugElement.nativeElement as HTMLElement;
            expect(myComponent.querySelector('app-authorization')).toBeTruthy();
        });

        it('should display home component if already logged in', () => {
            fixture = TestBed.createComponent(SearchFormComponent);
            component = fixture.componentInstance;
            component.isLoggedIn = true;
            fixture.detectChanges();

            const myComponent = fixture.debugElement.nativeElement as HTMLElement;
            expect(myComponent.querySelector('app-home')).toBeTruthy();
        });
    });
});

That's it, now we can make sure the component will behave as expected based on passed input by changing the condition every time the component is initialized.

Hope it was useful, please feel free to ask any questions in the comments.

36