简体   繁体   English

Mocking setTimeout with jest

[英]Mocking setTimeout with jest

I have a website made with React.JS that is continuously emitting events for everything [and nothing] that is happening.我有一个用 React.JS 制作的网站,它不断地为正在发生的所有事情 [和任何事情] 发出事件。 For example, users typed something in the form, emit an event.例如,用户在表单中输入内容,发出事件。 Users set focus on a field and do not do anything for some time, we again emit an event.用户将焦点放在一个字段上并且一段时间内不做任何事情,我们再次发出一个事件。 The idea is to understand customers' behavior.这个想法是了解客户的行为。

I have a button what used to work as <Button onClick={(e)=>handler(e)}/> .我有一个以前用作<Button onClick={(e)=>handler(e)}/>的按钮。 I had to debounce the button and change it to <Button onClick={(e)=>setTimeout(handler(e), 1000)}/> .我不得不去抖动按钮并将其更改为<Button onClick={(e)=>setTimeout(handler(e), 1000)}/> Otherwise, users did not understand what was happening.否则,用户不明白发生了什么。

Now, I am trying to adjust the tests written as:现在,我正在尝试将测试调整为:

  it('displays similar listings', async () => {
    const renderResult = renderVdp(renderParams);

    await new Promise((resolve) => setTimeout(resolve, 1000));
    await waitFor(async () => {
      expect(renderResult.getAllByText('2017 BMW M3')).toHaveLength(4);
    });
  });

I was able to keep the original tests working by adding the await new Promise((resolve) => setTimeout(resolve, 1000));我能够通过添加await new Promise((resolve) => setTimeout(resolve, 1000));来保持原始测试的工作。 before relevant tests.在相关测试之前。 Without it, tests receive the next emitted event, which is not the event emitted as a response to clicking on the button.没有它,测试会收到下一个发出的事件,这不是作为单击按钮的响应而发出的事件。

I would like to use jest timer mocking or something similar, instead of actually introducing delays inside my tests.我想使用开玩笑的计时器 mocking 或类似的东西,而不是在我的测试中实际引入延迟。 I have tried using jest.useFakeTimers() , but it does not work as I need it to.我曾尝试使用jest.useFakeTimers() ,但它不能按我的需要工作。 My expects are failing.我的期望失败了。

Suggestions?建议?

Once you use fake timers, you control when to run them.一旦你使用了假计时器,你就可以控制何时运行它们。 Try to run them after you render:渲染后尝试运行它们:

it('displays similar listings', async () => {
    jest.useFakeTimers()
    const renderResult = renderVdp(renderParams);
    jest.runAllTimers();
    expect(renderResult.getAllByText('2017 BMW M3')).toHaveLength(4);
  });

If that's not working, you can always use a more "aggressive" mock:如果这不起作用,您可以随时使用更“激进”的模拟:

global.setTimeout = jest.fn(cb => cb());

I would do it in the test setup beforeEach / beforeAll and rollback to original setTimeout in the test teardown afterEach / afterAll我会在测试设置beforeEach / beforeAll中执行此操作,并在测试拆解afterEach / afterAll中回滚到原始 setTimeout

It is hard to tell how the test should look like if I can't see the code.如果我看不到代码,很难说出测试应该是什么样子。

If your component works a little bit like this:如果您的组件有点像这样工作:

const SetTimeoutComponent = () => {
  const [content, setContent] = React.useState<string[]>([]);

  const handleButtonClick = (event: React.MouseEvent<HTMLElement>) => {
    setContent([
        '2017 BMW M3',
        '2017 BMW M3',
        '2017 BMW M3',
        '2017 BMW M3'
    ])
  }

  return (
    <div>
      <button onClick={(e)=>setTimeout(() => handleButtonClick(e), 1000)}>
        Set timeout with state update
      </button>
      <div>
        {content.map((item, index) => (
            <div key={index}>{item}</div>
        ))}
      </div>
    </div>
  );
};

then you can test it as follows:然后您可以按如下方式对其进行测试:

it('displays similar listings', async () => {
  render(<TestedComponent />);
  const setTimeoutButton = screen.getByRole('button');

  jest.useFakeTimers();
  fireEvent.click(setTimeoutButton);
  act(() => {
    jest.runAllTimers();
  });
  expect(screen.getAllByText('2017 BMW M3')).toHaveLength(4);

  jest.useRealTimers();
});

You can check more in my post .您可以在我的帖子中查看更多信息。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM