In the ngOnInit method of the class I am testing I call a function of a service which retruns an observable. I have implemented a mock for the service, but I'm trying to use a spy for this exact test case. In my understanding the spy would overwrite the mock implementation unless I call ".and.callThrough()" on the spy. The problem is that everytime the mock implementation still gets executed although I set up a spy for the function.
I tried moving the spy into the beforeEach section which did not help. Also I tried to use the spy without the ".and.callFake()" extension. But it didn't help.
spec.ts file:
fdescribe('AppComponent', () => {
let fixture;
let component;
let dataServiceMock: DataServiceMock;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [AppComponent],
providers: [{ provide: DataService, useClass: DataServiceMock }],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
fixture.detectChanges();
dataServiceMock = TestBed.get(DataService);
});
fit('should not show navigation if not logged in', async(() => {
spyOn(dataServiceMock,'getCurrentUser').and.callFake(() => {
console.log('IN CALL FAKE')
throwError(new Error('induced error'))
});
}));
implementation of service mock:
export class DataServiceMock {
currentUser: User;
private createValidUser() {
let validUser = new User();
validUser.username = 'valid';
validUser.password = 'valid';
validUser.role = 'valid';
this.currentUser = validUser;
}
public getCurrentUser(): Observable<User> {
this.createValidUser();
return of(this.currentUser);
}
ngOnInit of component that is tested:
ngOnInit(): void {
this.dataService.getCurrentUser().subscribe(user => {
this.currentUser = user;
console.log('received user:', this.currentUser)
})
}
I expect that the console log prints out "IN CALL FAKE" and throws the "induced error" but instead the console prints out "received user:" and the validUser that is created in the service mock.
This is just a timing problem. In your beforeEach()
you are executing fixture.detectChanges()
. This executes ngOnInit()
, see the docs for details. So the solution is to NOT call fixture.detectChanges()
there, but move it into the spec AFTER you have changed the return from getCurrentUser
.
Here is a working StackBlitz showing this test working. I also changed a couple of more details to get a working test:
throwError()
, however that caused further issues since you don't actually have any Observable error handling in your component so it makes no sense to test for that.return of({username: 'test'})
just to allow the .subscribe()
within your component's ngOnInit()
to set up something that could be tested - I then set up a simple expect which tested that component.currentUser.username
was set properly.async()
wrapper you had around this spec - since you are using synchronous Observables (created with of()
) for testing, there is no need for this.Here is the new spec from that StackBlitz:
it('should not show navigation if not logged in', () => {
spyOn(dataServiceMock,'getCurrentUser').and.callFake(() => {
console.log('IN CALL FAKE')
// throwError(new Error('induced error'))
return of({username: 'test'})
});
fixture.detectChanges();
expect(component.currentUser.username).toEqual('test');
});
I hope this helps.