Re-running Component Initialization Lifecycles in Angular Unit Tests

Alireza Razinejad - Dec 28 '21 - - Dev Community

Problem

In Angular unit testing, one common challenge is re-running a component's initialization lifecycles, specifically the OnInit lifecycle, to test different scenarios. Often, components behave differently based on certain conditions, and it's essential to test these scenarios to ensure proper functionality.

For example, let's consider a scenario where we want to display a login page or a home page based on the user's login status. The decision to show either page should happen during the component's initialization.

Solution

When building Angular components using the Angular CLI, we can generate a component and a boilerplate unit test using ng generate component MyComponent. The generated unit test file typically looks 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();
  });
});
Enter fullscreen mode Exit fullscreen mode

In this test setup, the key line is fixture.detectChanges(), which triggers the OnInit lifecycle in our component.

To test scenarios where our component behaves differently based on certain conditions, let's assume our component uses an @Input() property to determine the user's 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.doSomethingBasedOnLogIn();
  }

  // Other component logic...
}
Enter fullscreen mode Exit fullscreen mode

To test this component with different login scenarios, we need to create the component twice (inside the unit test) and pass in the isLoggedIn property accordingly. After setting the input, we run fixture.detectChanges() to trigger the component's initialization and change detection.

Here's how we can write our unit tests to cover these scenarios:

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('Behaves correctly based on "isLoggedIn" status', () => {
    it('should display login component if not logged in', () => {
      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', () => {
      component.isLoggedIn = true;
      fixture.detectChanges();

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

By setting the isLoggedIn property appropriately and re-running fixture.detectChanges() before each test, we can verify that our component behaves as expected under different conditions.

Conclusion

In conclusion, we have learned how to effectively re-run a component's initialization lifecycles, specifically the OnInit lifecycle, in Angular unit tests. By using the fixture.detectChanges() method, we can trigger the initialization process and test our components under various scenarios.

We explored a scenario where our component's behavior depends on an @Input() property representing the user's login status. Through unit tests, we demonstrated how to pass different values to this property and verify that the component responds correctly based on the input.

This approach allows us to create robust and comprehensive tests for our Angular components, ensuring they behave as expected under different conditions.

With a solid understanding of re-running initialization lifecycles, you can confidently write unit tests that cover various use cases, leading to more reliable and maintainable Angular applications.

I hope you find this blog helpful for your Angular unit testing journey! Happy coding!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player