对这段代码有什么想法吗
jest.useFakeTimers()
it('simpleTimer', async () => {
async function simpleTimer(callback) {
await callback() // LINE-A without await here, test works as expected.
setTimeout(() => {
simpleTimer(callback)
}, 1000)
}
const callback = jest.fn()
await simpleTimer(callback)
jest.advanceTimersByTime(8000)
expect(callback).toHaveBeenCalledTimes(9)
}
失败原因
Expected mock function to have been called nine times, but it was called two times.
但是,如果我从LINE-A中删除`await`,则测试通过。
Promise和Timer不好用吗?
我想原因可能是在等待第二个承诺的解决。
8条答案
按热度按时间bkkx9g8r1#
是的,你的方向是对的。
发生了什么
await simpleTimer(callback)
将等待simpleTimer()
返回的Promise解析,因此callback()
第一次被调用,setTimeout()
也被调用。jest.useFakeTimers()
用一个mock替换了setTimeout()
,这样mock就记录了它是用[ () => { simpleTimer(callback) }, 1000 ]
调用的。jest.advanceTimersByTime(8000)
运行() => { simpleTimer(callback) }
(因为1000 < 8000),它调用setTimer(callback)
,setTimer(callback)
第二次调用callback()
,并返回await
创建的Promise。setTimeout()
不会第二次运行,因为setTimer(callback)
的其余部分在PromiseJobs
队列中排队,并且没有机会运行。expect(callback).toHaveBeenCalledTimes(9)
失败,报告callback()
仅被调用两次。附加信息
这是一个好问题。它吸引了人们对JavaScript的一些独特特性以及它是如何在后台工作的注意。
消息队列
JavaScript使用消息队列。在运行库返回到队列以检索下一条消息之前,每条消息都运行到完成。像
setTimeout()
这样的函数将消息添加到队列中。作业队列
ES6引入了
Job Queues
,其中一个所需的作业队列是PromiseJobs
,它处理“对Promise结算的响应作业”。此队列中的任何作业都将 * 在当前消息完成后、下一消息开始前 * 运行。then()
在PromiseJobs
中对一个作业进行排队,当调用它的Promise解析时。async / await
async / await
只是promise和generators上的语法糖。async
总是返回一个Promise,而await
实际上将函数的其余部分 Package 在一个附加到Promise的then
回调中。定时器模拟
Timer Mocks的工作原理是在调用
jest.useFakeTimers()
时用mocks替换setTimeout()
等函数。这些模拟记录了调用它们时使用的参数。然后,当调用jest.advanceTimersByTime()
时,将运行一个循环,该循环将同步调用在经过的时间内调度的任何回调,包括在运行回调时添加的任何回调。换句话说,
setTimeout()
通常对必须等待到当前消息完成才能运行的消息进行排队。TimerMock允许回调在当前消息中同步运行。下面是一个演示上述信息的示例:
如何让Timer Mocks和Promises玩得开心
Timer Mocks将同步执行回调,但这些回调可能会导致作业在
PromiseJobs
中排队。幸运的是,让
PromiseJobs
中的所有挂起作业在async
测试中运行实际上非常容易,您所需要做的就是调用await Promise.resolve()
。这实际上会将测试的剩余部分排队在PromiseJobs
队列的末尾,并让队列中已经存在的所有内容首先运行。考虑到这一点,下面是测试的工作版本:
htzpubme2#
Brian Adams的answer是当场。
但是调用
await Promise.resolve()
似乎只能解决一个挂起的promise。在真实的世界中,测试具有多个异步调用的函数将是痛苦的,如果我们必须在每次迭代中反复调用这个表达式。
相反,如果你的函数有多个
await
,这样做会更容易:1.在某处创建此函数
Jest < v27
Jest >= v27
1.现在,在需要调用多个
await Promise.resolve()
的地方调用await flushPromises()
关于this GitHub问题的更多详细信息。
t30tvxxf3#
有一个用例我只是找不到解决方案:
测试看起来像:
基本上,除非计时器提前,否则操作不会解决。这里感觉像是一个循环依赖:promise需要定时器提前来解决,伪定时器需要promise来解决提前。
rmbxnbpk4#
我也遇到了同样的问题,最后直接使用了
@sinonjs/fake-timers
,因为它提供了clock.tickAsync()
函数,根据文档:tickAsync()还将中断事件循环,允许任何计划的promise回调在运行计时器之前执行。
工作示例现在变为:
lztngnrs5#
我更喜欢在复杂的测试中使用自己的假计时器。
测试:
blmhpbnm6#
我有一个带超时模式的重试:等待具有超时的promise若干次。我最终得到了以下解决方案,基于布莱恩·亚当斯的回答,如果它能对任何人有任何帮助的话。
4xy9mtcn7#
上面的真的很有帮助!对于那些试图用React钩子来做这件事的人来说(!下面的代码为我们工作:
evrscar28#
自Jestv29.5.0起可以使用jest.advanceTimersByTimeAsync(msToRun)
jest.advanceTimersByTime(msToRun)的异步等效项。它允许在运行计时器之前执行任何预定的promise回调。