Does fakeAsync guarantee promise completion after tick/flushMicroservice
Main issue is how to test that expected actions were performed once a Promise was completed, for example to test if a component correctly updates its states after receiving some remote content.
In the specs below, the dealWithIt()
simulates a logic performed in response to a completed promise (it updates variable and trigger "another async event").
it('Promises fulfilled by flushMicrotasks',fakeAsync((): void => {
let x = 1;
let y = 2;
let dealWithIt = function(p:Promise<number>) {
p.then( v => {
x = v;
Promise.resolve(v).then( v=> {y = v+1; });
});
};
let p = Promise.resolve(y);
dealWithIt(p);
flushMicrotasks();
//valid if promise handling completed
expect(x).toBe(2);
expect(y).toBe(3);
}));
it('Promises fulfilled by tick',fakeAsync((): void => {
let x = 1;
let y = 2;
let dealWithIt = function(p:Promise<number>) {
p.then( v => {
x = v;
Promise.resolve(v).then( v=> {y = v+1; });
});
};
let p = Promise.resolve(y);
dealWithIt(p);
tick();
//valid if promise handling completed
expect(x).toBe(2);
expect(y).toBe(3);
}));
And both tests pass. At the time of checking expectations the promise was dealt with correctly.
But, is it guaranteed? Or was I just lucky.
Are there any limitations, will all Promises created in the scope of fakeAsync be 'fired' by calling tick
or flushMicrotasks
.
Solution 1:
Using together fakeAsync
and tick
/ flushMicrotasks
allows you to simulate asynchronous processing but in a "synchronous" way. So it's guaranteed that the callback you specified in your then
method is executed before executed your expectations.
From the documentation:
- fakeAsync
Wraps a function to be executed in the fakeAsync zone:
- microtasks are manually executed by calling flushMicrotasks(),
- timers are synchronous, tick() simulates the asynchronous passage of time.
If there are any pending timers at the end of the function, an exception will be thrown.
- tick
export tick(millis?: number) : void
exported from angular2/testing defined in angular2/src/testing/fake_async.ts (line 84) Simulates the asynchronous passage of time for the timers in the fakeAsync zone.
The microtasks queue is drained at the very start of this function and after any timer callback has been executed.
- flushMicrotasks
export flushMicrotasks() : void
Flush any pending microtasks.
Here is the corresponding plunkr:
- https://plnkr.co/edit/ARGGaY?p=preview
Under the hood
In fact the fakeAsync
function creates a dedicated zone that intercepts asynchronous processing of functions setTimeout
, clearTimeout
, setInterval
, clearInterval
but also override the scheduleMicrotask
function. This way the fakeAsync
can completely control asynchronous processing and simulate asynchronous processing. It relies on the DelayedFunctionScheduler
from Jasmine.
Registering a callback on a promise using the then
method immediately queues a microtask by calling the scheduleMicrotask
function previously defined in the custom zone. This way this zone has the hand on when to execute the promise callback.
The flushMicrotasks
function simply iterates over the registered microtasks and execute them synchronously (included the previously registered callback of the promise). The tick
does the same but calls in addition the tick
method of the Jasmine scheduler.
This article could give more details:
- https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
You could have a look at the source code of the fakeAsync
:
- https://github.com/angular/angular/blob/master/modules/angular2/src/testing/fake_async.ts#L34
Solution 2:
So yes, as Thierry explained in his answer fakeAsync allows testing synchronously the business logic behind handling promises.
I just wanted to add, that the test for fakeAsync have even specs for promise related scenarios:
describe('Promise', () => {
it('should run asynchronous code', fakeAsync(() => {
...
it('should run chained thens', fakeAsync(() => {
...
it('should run Promise created in Promise', fakeAsync(() => {
I found those tests at: https://github.com/angular/angular/blob/master/packages/core/test/fake_async_spec.ts