3

I have this test that will result in the infamous "1 timer(s) still in the queue" error:

import {
discardPeriodicTasks,
  fakeAsync,
  flush,
  flushMicrotasks,
  tick
} from "@angular/core/testing";

describe("Sleep", () => {
  const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

  it("should sleep async", async () => {
    let slept = false;
    await sleep(0).then(() => (slept = true));
    expect(slept).toBeTruthy();
  });

  it("should sleep fakeAsync", fakeAsync(async () => {
    let slept = false;
    await sleep(0).then(() => (slept = true));
    flush();
    flushMicrotasks();
    discardPeriodicTasks();
    tick(1000);
    expect(slept).toBeTruthy();
  }));
});

No amount of flushing or ticking including the hints from this answer will get rid of the timer. What else can I do? The variant without fakeAsync() works fine.

Stackblitz: https://stackblitz.com/edit/test-jasmine-karma-fakeasync-timer?file=app/test.ts

mimo
  • 6,221
  • 7
  • 42
  • 50
Alex R
  • 808
  • 2
  • 10
  • 26

2 Answers2

2

For whatever reason, it works if you convert the sleep(0) Promise into an Observable instead.

it("should sleep fakeAsync", fakeAsync(async () => {
  let slept = false;
  //await sleep(0).then(() => (slept = true));
  from(sleep(0)).subscribe(() => (slept = true));
  expect(slept).toBeFalsy();
  tick(0);
  expect(slept).toBeTruthy();
}));

I ran into a similar problem with debounceTime from Rxjs where no amount of flush(), flushMicroTasks() or discardPeriodicTasks() would release the debounce. However in my case I was able to resolve my problem by making a call to tick() with a sufficiently large time value after my expectation had finished to allow the debounceTime to complete.

jones1008
  • 157
  • 1
  • 15
Taedrin
  • 445
  • 1
  • 4
  • 16
  • 1
    Thanks for the hint; it does solve the problem for the fake example I provided. Unfortunately, it does not solve my real problem of a native `async` buried somewhere in a library when replacing `Promise.all().then()` with `forkJoin().subscribe()`. It's still nicer with Observables, though – Alex R Apr 19 '21 at 09:36
2

I was able to solve the problem in stackblitz removing the await in fakeAsync, because the point of fakeAsync is run synchronously.

So, the modified working test is:

it("should sleep fakeAsync", fakeAsync(() => {
    let slept = false;
    sleep(100).then(() => (slept = true));
    flush();
    expect(slept).toBeTruthy();
  }));

You just need to use flush to process your timeout time synchronously and the test will pass as expected. Another answer to support my point about fakeAsync and async: Angular testing: using fakeAsync with async/await.

I was still getting this error in my test for nested timeout, a timeout inside a service that is inside a request subscribe. This solution doesn't solve my problem. So I dropped the fakeAsync approach and use the one suggested here: Test a function that contains a setTimeout() and finally I solved my problem.

Augusto Icaro
  • 553
  • 5
  • 15