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)
})
})
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.