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

Posted

技术标签:

【中文标题】匹配器错误:接收到的值必须是模拟或间谍函数【英文标题】:Matcher error: received value must be a mock or spy function 【发布时间】:2021-09-02 02:12:32 【问题描述】:

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

const onSubmit = (data) => 
  // ...
  setIsPopupActive(true);
  // ...
;

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

useEffect(() => 
  if (isPopupActive) 
    setTimeout(() => 
      setIsPopupActive(false);
    , 3000);
  
, [isPopupActive]);

在测试中,我想检查一下,弹出窗口是否在 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);
    );
  );

但是,我收到了错误:

expect(received).toHaveBeenCalledTimes(expected)

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

Received has type:  function
Received has value: [Function setTimeout]

我做错了什么?

【问题讨论】:

【参考方案1】:

以下方法有效

beforeEach(() => 
  jest.spyOn(global, 'setTimeout');
);

afterEach(() => 
  global.setTimeout.mockRestore();
);

it('Test if SetTimeout is been called', 
  global.setTimeout.mockImplementation((callback) => callback());
  expect(global.setTimeout).toBeCalledWith(expect.any(Function), 7500);
)

【讨论】:

【参考方案2】:

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

嗯,你可以通过两种方式解决你的问题。

    将 Jest 配置为使用旧版假计时器。在 jest.config.js 中,您可以添加一行(但它不适用于我):
module.exports = 
 // many of lines omited
 timers: 'legacy'
;
    为单独的测试套件甚至测试配置传统的假计时器:
jest.useFakeTimers('legacy');
describe('My awesome logic', () => 
// blah blah blah
);

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

【讨论】:

此处列出的选项 2 对我有用。我希望开发人员不要在没有相应文档的情况下发布新版本。如果文档还没有准备好,有什么急事??【参考方案3】:

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

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

【讨论】:

不幸的是,当我用timeoutSpy 替换setTimeout 时,它说接收到的函数根本没有被调用。 jest.runOnlyPendingTimers()jest.runAllTimers 也没有帮助。还有其他选择吗? 您是否尝试将“现代”参数作为字符串传递给jest.useFakeTimers() 我也有同样的问题,expect(setTimeout).toHaveBeenCalledTimes(1);在jestjs.io/fr/docs/timer-mocks 中明确提到...

以上是关于匹配器错误:接收到的值必须是模拟或间谍函数的主要内容,如果未能解决你的问题,请参考以下文章

C POSIX子级根据文件内容发送信号,父级将接收到的信号与文件匹配

Comet OJ 热身赛(K题)principal(括号匹配问题+stack模拟)

jQuery API 总结

JS不区分大小写匹配字符串高亮模拟浏览器Ctrl+F

多个连接字符串的同步模式匹配算法

获取错误列名称或提供的值的数量与表定义不匹配