是的,你在正确的轨道上。
会发生什么
await simpleTimer(callback) 将等待simpleTimer() 返回的 Promise 解决,因此callback() 被第一次调用,setTimeout() 也被调用。 jest.useFakeTimers() replaced setTimeout() with a mock 所以模拟记录它是用[ () => { simpleTimer(callback) }, 1000 ] 调用的。
jest.advanceTimersByTime(8000) 运行 () => { simpleTimer(callback) } (因为 1000 setTimer(callback) 第二次调用 callback() 并返回由 await 创建的 Promise。 setTimeout() 自setTimer(callback) is queued in the PromiseJobs queue 的其余部分以来没有第二次运行,并且没有机会运行。
expect(callback).toHaveBeenCalledTimes(9) 未能报告 callback() 只被调用了两次。
其他信息
这是个好问题。它提请注意 JavaScript 的一些独特特性以及它在底层的工作原理。
消息队列
JavaScript 使用a message queue。在运行时返回队列以检索下一条消息之前,每条消息都是run to completion。 setTimeout()add messages to the queue等函数。
作业队列
ES6 introduces Job Queues 并且所需的作业队列之一是 PromiseJobs,它处理“作为对承诺结算的响应的作业”。此队列中的所有作业在当前消息完成之后和下一条消息开始之前运行。
当调用它的 Promise 解析时,then() 在 PromiseJobs 中排队作业。
异步/等待
async / awaitis just syntactic sugar over promises and generators。 async 总是返回一个 Promise,await 基本上将函数的其余部分包装在一个附加到给定 Promise 的 then 回调中。
定时器模拟
Timer Mocks 在调用jest.useFakeTimers() 时由replacing functions like setTimeout() with mocks 工作。这些模拟记录了它们被调用的参数。然后,当调用 jest.advanceTimersByTime() 时,会运行一个循环,该循环会同步调用任何在经过的时间内安排的回调,包括在运行回调时添加的任何回调。
换句话说,setTimeout() 通常将必须等到当前消息完成才能运行的消息排队。 Timer Mocks 允许回调在当前消息中同步运行。
这是一个演示上述信息的示例:
jest.useFakeTimers();
test('execution order', async () => {
const order = [];
order.push('1');
setTimeout(() => { order.push('6'); }, 0);
const promise = new Promise(resolve => {
order.push('2');
resolve();
}).then(() => {
order.push('4');
});
order.push('3');
await promise;
order.push('5');
jest.advanceTimersByTime(0);
expect(order).toEqual([ '1', '2', '3', '4', '5', '6' ]);
});
如何让 Timer Mocks 和 Promise 发挥出色
Timer Mocks 将同步执行回调,但这些回调可能会导致作业在 PromiseJobs 中排队。
幸运的是,让PromiseJobs 中的所有待处理作业在async 测试中运行实际上很容易,您只需调用await Promise.resolve()。这实际上会将测试的剩余部分排在PromiseJobs 队列的末尾,并让队列中的所有内容先运行。
考虑到这一点,这是测试的工作版本:
jest.useFakeTimers()
it('simpleTimer', async () => {
async function simpleTimer(callback) {
await callback();
setTimeout(() => {
simpleTimer(callback);
}, 1000);
}
const callback = jest.fn();
await simpleTimer(callback);
for(let i = 0; i < 8; i++) {
jest.advanceTimersByTime(1000);
await Promise.resolve(); // allow any pending jobs in the PromiseJobs queue to run
}
expect(callback).toHaveBeenCalledTimes(9); // SUCCESS
});