6

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);
  });
Ajay Kumar Ganesh
  • 1,838
  • 2
  • 25
  • 33

2 Answers2

32

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.

Brian Adams
  • 43,011
  • 9
  • 113
  • 111
  • @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"](https://jestjs.io/docs/en/api#testname-fn-timeout) – Brian Adams Oct 07 '18 at 16:27
  • 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. – Ajay Kumar Ganesh Oct 07 '18 at 16:33
  • @BrianAdams, why the test does not work without `await Promise.resolve();`? – Asking Nov 24 '21 at 06:54
0

I think you just need to return the promise from the function like

test('promiseDelay delays for 1s',() => {
  jest.useFakeTimers();
  return Promise.resolve().then(() => jest.advanceTimersByTime(100));
});

and then spy the setTimeout to be called once.

Ishant Solanki
  • 205
  • 2
  • 13