在结合使用伪造的计时器和Promise时,让Jest测试框架(版本23.2.0)正常工作时遇到了一些麻烦。我要去哪里错了?
假设我有以下模块:
// timing.js
export const timeout = ms =>
new Promise(resolve => {
setTimeout(resolve, ms)
})
我的测试文件如下:
// 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()
})
})
这将失败,并显示以下输出:
● 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)
但是,如果我删除了承诺:
// timing.js
export const timeout = ms => ({
then: resolve => {
setTimeout(resolve, ms)
}
})
...测试将通过
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
更新
尽管这不是最优雅的解决方案,但我目前正在使用以下测试。它有效,但是我仍然对为什么原始版本不起作用感到好奇
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)
})
})
答案 0 :(得分:11)
您没有做错任何事情-目前无法正常工作-对不起。必须先进行以下操作,才能从我们的工作开始:
advanceTimeByTime(100)
的事情,并使它与promises一起工作。要点是,.then(spy)
仅被称为以后。
由于我们是志愿者-这些事情没有具体的时间表。我希望SimenB在未来2-3个月内进行合并,我将在下个月继续与V8团队联系。
您始终可以编写异步测试:
// 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);
});
如果您还有其他需要等待的时间,可以在超时后添加期望值。
答案 1 :(得分:5)
自jest@26.0.0
起,您可以在两个不同的伪计时器实现之间进行选择。
我发现jest.useFakeTimers('legacy')
适用于使用the flushPromises
workaround的Promises,但不适用于Date
,而jest.useFakeTimers('modern')
适用于Date
但不适用于await flushPromises()
自Date
起就无法解决。
我发现最好的解决方案是改用@sinonjs/fake-timers
,因为该解决方案可与Promises和import FakeTimers from "@sinonjs/fake-timers";
// Before tests:
const clock = FakeTimers.install();
// In tests:
await clock.tickAsync(100);
// After tests:
clock.uninstall();
一起使用,而没有任何变通方法或hack:
users
答案 2 :(得分:1)
在我的情况下,计时器回调调用了其他异步函数,因此其他解决方案对我不起作用。我最终得出结论,通过手动确保promise队列为空,所有异步代码都将运行完毕,并且我可以使测试正常进行:
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(...);
});