How do I test promise delays with jest?

12,029

promiseDelay returns a Promise that resolves after ms so call a spy in then and test to see if the spy has been called after different intervals:

describe('promiseDelay', () => {

  beforeEach(() => { jest.useFakeTimers(); });
  afterEach(() => { jest.useRealTimers(); });

  test('should not resolve until timeout has elapsed', async () => {
    const spy = jest.fn();
    promiseDelay(100).then(spy);  // <= resolve after 100ms

    jest.advanceTimersByTime(20);  // <= advance less than 100ms
    await Promise.resolve();  // let any pending callbacks in PromiseJobs run
    expect(spy).not.toHaveBeenCalled();  // SUCCESS

    jest.advanceTimersByTime(80);  // <= advance the rest of the time
    await Promise.resolve();  // let any pending callbacks in PromiseJobs run
    expect(spy).toHaveBeenCalled();  // SUCCESS
  });

});

Note that test code is synchronous and Timer Mocks make setTimeout synchronous but then queues a callback in PromiseJobs so any queued callbacks need to be allowed to run before testing if the spy has been called.

This can be done by using an async test function and calling await on a resolved Promise which effectively queues the rest of the test at the end of PromiseJobs allowing any pending callbacks to run before the test continues.

Additional information about how promises and fake timers interact is available in my answer here.

Share:
12,029
Ajay Kumar Ganesh
Author by

Ajay Kumar Ganesh

Another full stack developer. Loves Laravel !

Updated on June 09, 2022

Comments

  • Ajay Kumar Ganesh
    Ajay Kumar Ganesh almost 2 years

    Here's my code which I use to delay process (for backoff)

    export function promiseDelay(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
    

    I would want to test it but I'm not able to. I tried working with fakeTimers but my test never ends.

    test('promiseDelay delays for 1s', async (done) => {
        jest.useFakeTimers();
        Promise.resolve().then(() => jest.advanceTimersByTime(100));
        await promiseDelay(100);
      });
    
  • Brian Adams
    Brian Adams over 5 years
    @thegeekajay The test in the question provides the argument done to the test function but never calls it, that's the reason that particular test never finishes: "Jest will also wait if you provide an argument to the test function, usually called done"
  • Ajay Kumar Ganesh
    Ajay Kumar Ganesh over 5 years
    Thanks, i figured that out and fixed it. but the test 'should not resolve if < 100ms' is always failing. coz the spy function was called.
  • Asking
    Asking over 2 years
    @BrianAdams, why the test does not work without await Promise.resolve();?