DEV Community

Alireza Razinejad
Alireza Razinejad

Posted on • Edited on

Re-running Component Initialization Lifecycles in Angular Unit Tests

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!

Top comments (6)

Collapse
 
skynetigor profile image
Igor Panasiuk

Looks too complicated as of me. Why not remove fixture.detectChanges from beforeEach where fixture is created and then just call fixture.detectChanges in tests?

Collapse
 
ussdlover profile image
Alireza Razinejad

Thank you for the review, I've just updated the post and hope you would give it another chance.

Collapse
 
skynetigor profile image
Igor Panasiuk • Edited

Great, looks good. But I think it still has a mistake.
This fixture.detectChanges() will call ngOnInitin beforeEach and further fixture.detectChanges calls won't trigger ngOnInit and test will fail.
I believe the purpose is not to call fixture.detectChanges() automatically before each test but explicitly call fixture.detectChanges() in tests. Does it make sense?

Image description

Thread Thread
 
ussdlover profile image
Alireza Razinejad

Yea, that makes sense! I guess when I wrote this post, the detectChanges within the test did work as expected by recalling the onInit.
Have you seen a test like this has failed?

Collapse
 
leoniiii profile image
Leonides Muguercia

I'm sorry, i just don't understand where did Search component this component come from?

Collapse
 
ussdlover profile image
Alireza Razinejad

It should be MyComponent, sorry for the error