是的,你在正确的轨道上。
怎么了
await simpleTimer(callback)
将等待返回的 PromisesimpleTimer()
来解决,因此callback()
第一次setTimeout()
被调用并且也会被调用。 jest.useFakeTimers()
替换setTimeout()
为模拟,因此模拟记录了它被调用的情况[ () => { simpleTimer(callback) }, 1000 ]
。
jest.advanceTimersByTime(8000)
运行() => { simpleTimer(callback) }
(因为 1000 < 8000) 调用setTimer(callback)
which 调用callback()
第二次并返回由await
. setTimeout()
不会第二次运行,因为其余部分setTimer(callback)
已在PromiseJobs
队列中排队并且没有机会运行。
expect(callback).toHaveBeenCalledTimes(9)
报告callback()
只调用了两次失败。
附加信息
这是一个很好的问题。它引起了人们对 JavaScript 的一些独特特征及其背后工作方式的关注。
消息队列
JavaScript 使用消息队列。在运行时返回队列以检索下一条消息之前,每条消息都会运行到完成。像setTimeout()
向队列添加消息这样的功能。
作业队列
ES6 引入了Job Queues
一个必需的作业队列,PromiseJobs
它处理“作为对 Promise 解决的响应的作业”。此队列中的所有作业在当前消息完成之后和下一条消息开始之前运行。
当调用它的 Promise 解决时,then()
将作业排队PromiseJobs
。
异步/等待
async / await
只是 promises 和 generators 的语法糖。 async
总是返回一个 Promise 并且await
本质上将函数的其余部分包装在then
附加到它给定的 Promise的回调中。
定时器模拟
定时器嘲笑的工作更换喜欢的功能setTimeout()
与嘲笑的时候jest.useFakeTimers()
被调用。这些模拟记录了它们被调用的参数。然后,当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 和 Promises 发挥出色
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
});