Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does fakeAsync guarantee promise completion after tick/flushMicroservice

Tags:

angular

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.

like image 245
Zielu Avatar asked Mar 14 '16 14:03

Zielu


2 Answers

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
like image 190
Thierry Templier Avatar answered Nov 03 '22 00:11

Thierry Templier


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

like image 45
Zielu Avatar answered Nov 02 '22 23:11

Zielu