I'm having a little trouble getting the Jest testing framework (version 23.2.0) to work nicely when using a combination of fake timers and promises. Where am I going wrong?
Let's say I have the following module:
// timing.js
export const timeout = ms =>
new Promise(resolve => {
setTimeout(resolve, ms)
})
And my test file looks like:
// timing.test.js
import { timeout } from './timing'
describe('timeout()', () => {
beforeEach(() => {
jest.useFakeTimers()
})
it('resolves in a given amount of time', () => {
const spy = jest.fn()
timeout(100).then(spy)
expect(spy).not.toHaveBeenCalled()
jest.advanceTimersByTime(100)
expect(spy).toHaveBeenCalled()
})
})
This fails with the following output:
● timeout › resolves in a given amount of time
expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called, but it was not called.
15 |
16 | jest.advanceTimersByTime(100)
> 17 | expect(spy).toHaveBeenCalled()
| ^
18 | })
19 | })
20 |
at Object.<anonymous> (src/timing.test.js:17:17)
However, if I remove the promise:
// timing.js
export const timeout = ms => ({
then: resolve => {
setTimeout(resolve, ms)
}
})
... the test will pass
timeout
✓ resolves in a given amount of time (5ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.304s
UPDATE
Although it's not the most elegant solution, I'm currently using the below test instead. It works, but I'm still intrigued why the original one didn't
import { timeout } from './timing'
describe('timeout', () => {
it('resolves in a given amount of time', done => {
setTimeout(() => done(new Error('it didn\'t resolve or took longer than expected')), 10)
return timeout(9).then(done)
})
})
Enable Fake TimersuseFakeTimers() . This is replacing the original implementation of setTimeout() and other timer functions. Timers can be restored to their normal behavior with jest. useRealTimers() .
In order to mock asynchronous code in Jest, more specifically Promises, you can use the mockResolvedValue function. This will mock the return value of the Promise to be 42. In order to test a Promise in Jest, you need to turn your it block into async in order to use the await keyword in front of an expect statement.
jest-date-mock is a complete javascript module wrote by me, and it is used to test Date on jest. import { advanceBy, advanceTo } from 'jest-date-mock'; test('usage', () => { advanceTo(new Date(2018, 5, 27, 0, 0, 0)); // reset to date time. const now = Date.
Use Jest. Settimeout(Newtimeout) To Increase The Timeout Value, If This Is A Long-Running Test.” will be demonstrated using examples from the programming language. jest. setTimeout(30000);
The current best alternative is to use the async versions of fake-timers. So you would do
await clock.tickAsync(1000); // doesn't wait 1000ms but is async
Instead of calling clock.tick
. Please see the answer below for more details.
You're not doing anything wrong - it doesn't work at the moment - sorry. The following things have to happen before this will work from our end:
advanceTimeByTime(100)
and have that work with promises.The problem in a gist is that the .then(spy)
only gets called later.
As we are volunteers - there is no concrete timeline for these things. I hope SimenB does the merge in the coming 2-3 months and I'll follow up with the hook with the V8 team next month.
You can always write an async test:
// note this is an async function now it('resolves in a given amount of time', async () => { // this is in a promise.reoslve.then to not 'lock' on the await Promise.resolve().then(() => jest.advanceTimersByTime(100)); await timeout(100); });
You can add expectations after the timeout if there is anything else you want to wait for.
Since [email protected]
you can choose between two different fake timer implementations.
I found that jest.useFakeTimers('legacy')
works with Promises using the flushPromises
workaround, but it doesn't work with Date
, whereas jest.useFakeTimers('modern')
works with Date
but not with Promises since await flushPromises()
never resolves.
The best solution I found was to use @sinonjs/fake-timers
instead, since that one works with both Promises and Date
without any sort of workarounds or hacks:
import FakeTimers from "@sinonjs/fake-timers"; // Before tests: const clock = FakeTimers.install(); // In tests: await clock.tickAsync(100); // After tests: clock.uninstall();
In my case the timer callback called other async functions so the other solution wasn't working for me. I ended up working out that by manually ensuring the promise queue was empty, all the async code will have finished running and I could get the tests to work:
function flushPromises() {
// Wait for promises running in the non-async timer callback to complete.
// From https://stackoverflow.com/a/58716087/308237
return new Promise(resolve => setImmediate(resolve));
}
test('example', async () => {
jest.useFakeTimers();
example_function_to_set_a_timer();
// Wait for one virtual second
jest.advanceTimersByTime(1000);
// Wait for any async functions to finish running
await flushPromises();
// Continue with tests as normal
expect(...);
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With