匹配器错误:接收的值必须是模拟或间谍函数

Vel*_*ack 5 javascript reactjs jestjs react-testing-library

我正在为表单 React 组件编写测试(使用 Jest 和 React 测试库)。我有一个在表单提交上运行的方法:

const onSubmit = (data) => {
  // ...
  setIsPopupActive(true);
  // ...
};
Run Code Online (Sandbox Code Playgroud)

并且useEffectisPopupActive更改后运行,所以也在提交时:

useEffect(() => {
  if (isPopupActive) {
    setTimeout(() => {
      setIsPopupActive(false);
    }, 3000);
  }
}, [isPopupActive]);
Run Code Online (Sandbox Code Playgroud)

在测试中,我想检查一下,弹出窗口是否在 3 秒后消失。所以这是我的测试:

it('Closes popup after 3 seconds', async () => {
    const nameInput = screen.getByPlaceholderText('Imi?');
    const emailInput = screen.getByPlaceholderText('Email');
    const messageInput = screen.getByPlaceholderText('Wiadomo??');
    const submitButton = screen.getByText('Wy?lij');

    jest.useFakeTimers();

    fireEvent.change(nameInput, { target: { value: 'Test name' } });
    fireEvent.change(emailInput, { target: { value: 'test@test.com' } });
    fireEvent.change(messageInput, { target: { value: 'Test message' } });
    fireEvent.click(submitButton);

    const popup = await waitFor(() =>
      screen.getByText(/Wiadomo?? zosta?a wys?ana/)
    );

    await waitFor(() => {
      expect(popup).not.toBeInTheDocument(); // this passes

      expect(setTimeout).toHaveBeenCalledTimes(1);
      expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 3000);
    });
  });
Run Code Online (Sandbox Code Playgroud)

但是,我收到错误:

expect(received).toHaveBeenCalledTimes(expected)

Matcher error: received value must be a mock or spy function

Received has type:  function
Received has value: [Function setTimeout]
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?

小智 8

Jest 27对 fakeTimers 进行了重大更改。似乎 Jest 贡献者没有及时更新文档。关于 Github 问题的这条评论证实了这一点。此外,这里相关的公关。

那么,您可以通过两种方式解决您的问题。

  1. 配置 Jest 以使用传统的假计时器。在jest.config.js 中,您可以添加行(但对我不起作用):
module.exports = {
 // many of lines omited
 timers: 'legacy'
};
Run Code Online (Sandbox Code Playgroud)
  1. 为单独的测试套件配置旧的假计时器,甚至测试:
jest.useFakeTimers('legacy');
describe('My awesome logic', () => {
// blah blah blah
});
Run Code Online (Sandbox Code Playgroud)

最好使用基于@sinonjs/fake-timers 的新语法。但是我找不到Jest 的工作示例,所以我会尽快更新这个答案。


Ily*_*ski 0

在你的情况下setTimeout,它不是一个模拟或间谍,而是一个真正的功能。要使其成为间谍,请使用const timeoutSpy = jest.spyOn(window, 'setTimeout'). timeoutSpy并在断言中使用。

您还可以测试不调用该函数的事实setTimeout,而是断言该函数setIsPopupActive被调用一次,并且使用false. 为此,您可能需要执行jest.runOnlyPendingTimers()jest.runAllTimers()

  • 我有同样的问题和expect(setTimeout).toHaveBeenCalledTimes(1); https://jestjs.io/fr/docs/timer-mocks 中明确提到... (2认同)