Angular testing how to prevent ngOnInit call to test a method directly Angular testing how to prevent ngOnInit call to test a method directly typescript typescript

Angular testing how to prevent ngOnInit call to test a method directly


Preventing lifecycle hook (ngOnInit) from being called is a wrong direction. The problem has two possible causes. Either the test isn't isolated enough, or testing strategy is wrong.

Angular guide is quite specific and opinionated on test isolation:

However, it's often more productive to explore the inner logic of application classes with isolated unit tests that don't depend upon Angular. Such tests are often smaller and easier to read, write, and maintain.

So isolated tests just should instantiate a class and test its methods

userManagementService = new UserManagementServiceStub;comp = new UserListComponent(userManagementService);spyOn(comp, 'getUserList');...comp.ngOnInit();expect(comp.getUserList).toHaveBeenCalled();...comp.onRefreshUserList();expect(comp.getUserList).toHaveBeenCalled();

Isolated tests have a shortcoming - they don't test DI, while TestBed tests do. Depending on the point of view and testing strategy, isolated tests can be considered unit tests, and TestBed tests can be considered functional tests. And a good test suite can contain both.

In the code above should get the user List via refresh function test is obviously a functional test, it treats component instance as a blackbox.

A couple of TestBed unit tests can be added to fill the gap, they probably will be solid enough to not bother with isolated tests (although the latter are surely more precise):

spyOn(comp, 'getUserList');comp.onRefreshUserList();expect(comp.getUserList).toHaveBeenCalledTimes(1);...spyOn(comp, 'getUserList');spyOn(comp, 'ngOnInit').and.callThrough();tick();fixture.detectChanges(); expect(comp.ngOnInit).toHaveBeenCalled();expect(comp.getUserList).toHaveBeenCalledTimes(1);


it(`should get the user List via refresh function`, fakeAsync(() => {  let ngOnInitFn = UserListComponent.prototype.ngOnInit;  UserListComponent.prototype.ngOnInit = () => {} // override ngOnInit  comp.onRefreshUserList();  tick();  fixture.detectChanges();   UserListComponent.prototype.ngOnInit = ngOnInitFn; // revert ngOnInit  expect(comp.userList.length).toBe(3, 'user list after function call');}));

Plunker Example


I personally prefer cancelling the component ngOnInit for every test.

beforeEach(() => {    UserListComponent.prototype.ngOnInit = () => {} ;   ....  });