我正在为 React 组件编写 Jest 测试,并使用
@testing-library/user-event
库来模拟用户交互。这非常有效,除了在使用 Jest 的假计时器的测试中。
这是一个示例测试:
it(`fires onClick prop function when the button is clicked`, async () => {
// jest.useFakeTimers()
let propFn = jest.fn()
let app = RTL.render(
<SampleComp onClick={propFn} />
)
await userEvent.click(app.queryByText('Test button')!)
expect(propFn).toHaveBeenCalled()
// jest.useRealTimers()
})
这是组件:
function SampleComp({ onClick }) {
// a simple bridge for debugging
function _onClick(e) {
console.log(`_onClick invoked`)
return onClick(e)
}
return (
<button onClick={_onClick}>
Test button
</button>
)
}
如果没有假计时器,测试将在不到一秒的时间内运行并通过。使用假定时器,测试超时然后失败:
● fires onClick prop function when the button is clicked
thrown: "Exceeded timeout of 5000 ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
它也无法发出调试行,因此显然单击事件从未被触发。
我已经完成了大量调试,并确定根本原因是用户事件库内部依赖于
setTimeout
在事件之间创建短暂的延迟。
是的,“事件”复数——回想一下用户事件库的价值主张是:
调度 DOM 事件,而fireEvent
模拟完整的 交互,这可能会触发多个事件并在此过程中进行额外的检查。user-event
-- 用户事件文档
在这种情况下,用户事件为我创建了两个事件:
1虽然第一个事件的用户事件的内部事件对象只有
target
而没有 type
,但对我来说第一个事件是鼠标移动或鼠标悬停在按钮上是有意义的。无论如何:我已经验证用户事件内部创建的操作列表确实有 2 项,第二项明确标记为“[MouseLeft]”。
pointerAction
函数在事件之间插入延迟。就我而言,用户事件在事件 1 和 2 之间的延迟期间停止。
超时是在 user-event 的
wait
模块中创建的:
new Promise<void>(resolve => globalThis.setTimeout(() => resolve(), delay))
我已经通过在我的node_modules中对该行进行猴子修补来验证这就是问题所在,以便在没有计时器的情况下立即解决,如下所示:
new Promise<void>(resolve => resolve())
不幸的是,我的应用程序代码有一些重要的计时器,它无法让测试套件实时等待它们。即使我可以,我怀疑后来发现这个问题的人可能没有这种自由。
运行 Jest 的假计时器来允许用户事件继续进行是行不通的,如下所示:
it(`fires onClick prop function when the button is clicked`, async () => {
jest.useFakeTimers()
let propFn = jest.fn()
let app = RTL.render(
<SampleComp onClick={propFn} />
)
let userAction = userEvent.click(app.queryByText('Test button')!)
jest.runOnlyPendingTimers() // no good
jest.runAllTimers() // also no good
await userAction
expect(propFn).toHaveBeenCalled()
jest.useRealTimers()
})
那么,如何将 Jest 的假定时器与用户事件库结合使用呢?
在这种情况下,版本似乎确实很重要:这个测试对于早期版本的 React、Jest 和用户事件效果很好。以下是我现在使用的版本(其中出现问题的地方),以及我曾经使用的版本(此处有效的地方):
我应该注意,无法更改正在使用的软件包版本。我需要一个适用于“当前”列中列出的特定版本的解决方案。
注意:我认为这个问题并不是 React 特有的,使用 Vue 或 Svelte 或其他任何东西的人只要使用 Jest 的假计时器 + 用户事件(两者都是框架无关的)就会遇到同样的问题。所以,我不会将其标记为 React。
你尝试过这个吗:
const ue = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
在我的例子中,如果没有设置它就不起作用。更多这里用 Jest 模拟 setTimeout
更新: 在
user-event
v14.1 及更高版本中,您可以使用 advanceTimers 选项来提前假计时器:
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime })
或者更明确地说:
const user = userEvent.setup({
advanceTimers: (delay) => {
jest.advanceTimersByTime(delay)
}
})
user-event
在模拟输入事件之间的延迟时调用 advanceTimers
函数。通过调用 jest.advanceTimersByTime
,我们提前假定时器并且测试不会超时。
之前的回答:(不再推荐)
您可以使用user-event
设置功能禁用
延迟:
const user = userEvent.setup({ delay: null })
确保使用
setup()
: 返回的实例
await user.click(app.queryByText('Test button')!)