简体   繁体   中英

Mocking setTimeout with jest

I have a website made with React.JS that is continuously emitting events for everything [and nothing] that is happening. 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)}/> . I had to debounce the button and change it to <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)); 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. I have tried using jest.useFakeTimers() , but it does not work as I need it to. 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

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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