4

Trying to do a relatively simple assertion with jest. I have the following test setup:

const sleep = ms => new Promise(res => setTimeout(res, ms));

it('should call callback after specified delay', async () => {
  const mockCallback = jest.fn();

  setTimeout(1000, mockCallback);

  expect(mockCallback).not.toHaveBeenCalled();

  await sleep(1000);

  expect(mockCallback).toHaveBeenCalled();
});

When I run the test fails with the following error:

Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.

Obviously this is nowhere near that threshold. Any idea what I'm doing wrong?

[UPDATE] I realized previously I had called jest.useFakeTimers() before the test. After removing this and running the test again I still get a failure but it's not a timeout. Instead just simply

Expected mock function to have been called, but it was not called.

Note this is also the case when significantly increasing the sleep to 4000ms.

If instead I switch from setTimeout to

sleep(ONE_SECOND)
  .then(mockCallback);

the test passes. Jest clearly modifies how setTimeout interacts in parallel with Promises, but it's not obvious as to what is going on.

amdilley
  • 187
  • 1
  • 1
  • 8
  • You probably should `sleep` a bit longer than the `setTimeout` that you expect to get called – Bergi Apr 13 '19 at 18:33
  • I can't reproduce the problem. Maybe you are using a version of Jest that predates promise support and you should upgrade it? – Quentin Apr 13 '19 at 18:38
  • Have tried setting longer sleep periods (up to 4000ms) and no go. jest version is 24.4.0 which I believe supports async/await. I'm successfully using async/await elsewhere with enzyme – amdilley Apr 13 '19 at 19:57
  • Never used Jest, but it seems like it's expecting you to use the callback provided as the argument to your function (the parameter is usually called `done`). So you'd have to replace `async ()` with `async (done)` and use `done()` when the test is supposed to be completed. But I have no idea why it's expecting this here (I have used similar test frameworks, but here I'm a bit confused). Perhaps it hooks into `setTimeout` and if that's used, it decides that this is an asynchronous test and needs to be completed with `done()`? – rhino Apr 13 '19 at 20:13
  • Anyway, if the sleep duration is too short, the assertion should simply fail, so I don't think this has anything to do with the issue. – rhino Apr 13 '19 at 20:20

1 Answers1

3

You just need to pass mockCallback as the first argument to setTimeout:

const sleep = ms => new Promise(res => setTimeout(res, ms));

it('should call callback after specified delay', async () => {
  const mockCallback = jest.fn();

  setTimeout(mockCallback, 1000);  // <= pass mockCallback as first argument

  expect(mockCallback).not.toHaveBeenCalled();  // Success!

  await sleep(1000);

  expect(mockCallback).toHaveBeenCalled();  // Success!
});
Brian Adams
  • 43,011
  • 9
  • 113
  • 111
  • 1
    ...but yes, `Promises` and timers can be tricky to test together, more detailed info [available here](https://stackoverflow.com/questions/52177631/jest-timer-and-promise-dont-work-well-settimeout-and-async-function/52196951#52196951) – Brian Adams Apr 14 '19 at 01:40