如何将用户事件库与 Jest 的假定时器结合使用?

问题描述 投票:0回答:2

我正在为 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
在事件之间创建短暂的延迟。

是的,“事件”复数——回想一下用户事件库的价值主张是:

fireEvent
调度 DOM 事件,而
user-event
模拟完整的 交互,这可能会触发多个事件并在此过程中进行额外的检查。
-- 用户事件文档

在这种情况下,用户事件为我创建了两个事件:

  1. 鼠标移动1
  2. 实际点击

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 和用户事件效果很好。以下是我现在使用的版本(其中出现问题的地方),以及我曾经使用的版本(此处有效的地方):

图书馆 当前
(中断)
上一个
(已工作)
开玩笑 v27.5.1 v26.6.3
反应 v18.1.0 v16.13.1
@测试库/用户事件 v14.3.0 v12.8.3

我应该注意,无法更改正在使用的软件包版本。我需要一个适用于“当前”列中列出的特定版本的解决方案。

注意:我认为这个问题并不是 React 特有的,使用 Vue 或 Svelte 或其他任何东西的人只要使用 Jest 的假计时器 + 用户事件(两者都是框架无关的)就会遇到同样的问题。所以,我不会将其标记为 React。

javascript unit-testing jestjs settimeout user-event
2个回答
7
投票

你尝试过这个吗:

const ue = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });

在我的例子中,如果没有设置它就不起作用。更多这里用 Jest 模拟 setTimeout


5
投票

更新:

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')!)
© www.soinside.com 2019 - 2024. All rights reserved.