Here's an abstract from Testing Asynchronous Code - CodeCraft
Consider this piece of code:
it('Button label via async() and whenStable()', async(() => {
fixture.detectChanges();
expect(el.nativeElement.textContent.trim()).toBe('Login');
spyOn(authService, 'isAuthenticated').and.returnValue(Promise.resolve(true));
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(el.nativeElement.textContent.trim()).toBe('Logout');
});
component.ngOnInit();
}));
async
function executes the code inside its body in a special async
test zone. This intercepts and keeps track of all promises created in its body.
Only when all of those pending promises have been resolved does it then resolves the promise returned from whenStable
.
You can use this to avoid using Jasmine's spy mechanism of detecting when a promise has been resolved.
This mechanism is slightly better than using the plain Jasmine solution but there is another version which gives us fine grained control and also allows us to lay out our test code as if it were synchronous.
Now consider this piece of code:
it('Button label via fakeAsync() and tick()', fakeAsync(() => {
expect(el.nativeElement.textContent.trim()).toBe('');
fixture.detectChanges();
expect(el.nativeElement.textContent.trim()).toBe('Login');
spyOn(authService, 'isAuthenticated').and.returnValue(Promise.resolve(true));
component.ngOnInit();
tick();
fixture.detectChanges();
expect(el.nativeElement.textContent.trim()).toBe('Logout');
}));
Just like async
, the fakeAsync
function executes the code inside its body in a special fake async
test zone. This intercepts and keeps track of all promises created in its body.
The tick()
function blocks execution and simulates the passage of time until all pending asynchronous activities complete.
So when we call tick()
the application sits and waits for the promises to be resolved and then lets execution move to the next line.
The main advantage of using this is that it makes the code more linear as if we were executing synchronous code, there are no callbacks to confuse the mind and everything is simpler to understand.
Conclusion:
There are three mechanisms we can use to test asynchronous code:
The jasmine's done
function and spy callbacks. This works but expects us to know about all the promises in our application and be able to hook into them.
We can use the Angular async
and whenStable
functions, we don’t need to track the promises ourselves but we still need to lay our code out via callback functions which can be hard to read.
We can use the Angular fakeAsync
and tick
functions, this additionally lets us lay out our async
test code as if it were synchronous.
These points would make you think that why use async
+ whenStable
if it's hard to read. Why not simply use fakeAsync
+ tick
instead? Well one of the reasons would be beause of this:
Important
fakeAsync
does have some drawbacks, it doesn’t track XHR requests for instance.
You can read more about this on this GitHub Thread.