Angular 2 fakeAsync waiting for timeout in a function using tick()?

39,128

Solution 1

The purpose of fakeAsync is to control time within your spec. tick will not wait for any time as it is a synchronous function used to simulate the passage of time. If you want to wait until the asynchronous function is complete, you are going to need to use async and whenStable, however, in your example, the spec will take 3 seconds to pass so I wouldn't advise this.

The reason why the counter.spec.ts is failing is that you have only simulated the passage of 0 seconds (typically used to represent the next tick of the event loop). So when the spec completes, there are still mocked timers active and that fails the whole spec. It is actually working properly by informing you that a timeout has been mocked an is unhandled.

Basically, I think you are attempting to use fakeAsync and tick in ways for which they were not intended to be used. If you need to test a timeout the way that you have proposed, the simplest way would be to mock the setTimeout function yourself so that, regardless of the time used, you can just call the method.

EDITED I ran into a related issue where I wanted to clear the timers, and since it was not the part under test, I didn't care how long it took. I tried:

tick(Infinity);

Which worked, but was super hacky. I ended up going with

discardPeriodicTasks();

And all of my timers were cleared.

Solution 2

Try to add one or a combination of the following function calls to the end of your test:

    flush();
    flushMicrotasks();
    discardPeriodicTasks();
  1. flush (with optional maxTurns parameter) also flushes macrotasks. (This function is not mentionned in the Angular testing tutorial.)
  2. flushMicrotasks flushes the microtask queue.
  3. discardPeriodicTasks cancels "peridodic timer(s) still in the queue".

Timers in the queue do not necessarily mean that there's a problem with your code. For example, components that observe the current time may introduce such timers. If you use such components from a foreign library, you might also consider to stub them instead of "chasing timers".

For further understanding you may look at the javascript code of the fakeAsync function in zone-testing.js.

Solution 3

At the end of each test add:

 fixture.destroy();
 flush();

Solution 4

Try this:

// I had to do this:
it('timeout (fakeAsync/tick)', (done) => {
  fixture.whenStable().then(() => {
       counter.getTimeout();
       tick();
    done();
  });
});

Source

Solution 5

Async

test.service.ts

export class TestService {
  getTimeout() {
    setTimeout(() => { console.log("test") }, 3000);
  }
}

test.service.spec.ts

import { TestBed, async } from '@angular/core/testing';

describe("TestService", () => {
  let service: TestService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [TestService],
    });

    service = TestBed.get(TestService);
  });

  it("timeout test", async(() => {
    service.getTimeout();
  });
});

Fake Async

test.service.ts

export class TestService {
  readonly WAIT_TIME = 3000;

  getTimeout() {
    setTimeout(() => { console.log("test") }, this.WAIT_TIME);
  }
}

test.service.spec.ts

import { TestBed, fakeAsync } from '@angular/core/testing';

describe("TestService", () => {
  let service: TestService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [TestService],
    });

    service = TestBed.get(TestService);
  });

  it("timeout test", fakeAsync(() => {
    service.getTimeout();
    tick(service.WAIT_TIME + 10);
  });
});
Share:
39,128
nipuna777
Author by

nipuna777

I love all things tech. I'm currently working with mobile and web technologies.

Updated on July 28, 2022

Comments

  • nipuna777
    nipuna777 almost 2 years

    I'm trying to get the results from a mock backend in Angular 2 for unit testing. Currently, we are using fakeAsync with a timeout to simulate the passing of time.

    current working unit test

    it('timeout (fakeAsync/tick)', fakeAsync(() => {
        counter.getTimeout();
        tick(3000); //manually specify the waiting time
    }));
    

    But, this means that we are limited to a manually defined timeout. Not when the async task is completed. What I'm trying to do is getting tick() to wait until the task is completed before continuing with the test.

    This does not seem to work as intended.

    Reading up on the fakeAsync and tick the answer here explains that:

    tick() simulates the asynchronous passage of time.

    I set up a plnkr example simulating this scenario.

    Here, we call the getTimeout() method which calls an internal async task that has a timeout. In the test, we try wrapping it and calling tick() after calling the getTimeout() method.

    counter.ts

    getTimeout() {
      setTimeout(() => {
        console.log('timeout')
      },3000)
    }
    

    counter.specs.ts

    it('timeout (fakeAsync/tick)', fakeAsync(() => {
        counter.getTimeout();
        tick();
    }));
    

    But, the unit test fails with the error "Error: 1 timer(s) still in the queue."

    Does the issue here in the angular repo have anything to do with this?

    Is it possible to use tick() this way to wait for a timeout function? Or is there another approach that I can use?

  • Walter Luszczyk
    Walter Luszczyk about 5 years
    It bring waiting tasks from 3 to 1. But one still is in queue.
  • Drenai
    Drenai over 4 years
    Tried everything, but discardPeriodicTasks() worked perfect
  • Samih
    Samih over 3 years
    I had to use both flush() and disacrdPeriodicTasks() to do the trick. Wish I had some idea what the hell is going on but hey ho :-).
  • user11883568
    user11883568 over 3 years
    Just got the case that a superfluous fakeAsync around my test caused it to fail with a "timer still in the queue". Removing the fakeAsync fixed the test.
  • user11883568
    user11883568 over 3 years
    Sometimes a deep debugging session with the zone code can explain such a strange behaviour and reveal the pending tasks, but only if you have much time...
  • aquilesb
    aquilesb over 3 years
    Just to save time for who is here, discardPeriodicTasks is in @angular/core/testing