user-event: `userEvent.click` fails due to timeout when used with `jest.useFakeTimers`

  • @testing-library/user-event version: 14.0.0-beta.7
  • Testing Framework and version: jest version: 27.4.7, @testing-library/jest-dom version 5.15.1, @testing-library/react version 12.1.2
  • DOM Environment: jsdom version 19.0.0

Relevant code or config

// Dummy.js
import React from "react";

const Dummy = (props) => {
  const clickHandler = () => {
    setTimeout(() => {
      props.onClick();
    }, 500);
  };

  return <button onClick={clickHandler}>Click me</button>;
};

export default Dummy;
// Dummy.test.js
import React from "react";

import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import userEvent from "@testing-library/user-event";

import Dummy from "./Dummy";

// Fails
test("onClick prop is called on button (userEvent, fakeTimer)", async () => {
  jest.useFakeTimers();
  const user = userEvent.setup();
  const onClick = jest.fn();
  render(<Dummy onClick={onClick} />);

  const button = screen.getByRole("button");
  await user.click(button);
  jest.runOnlyPendingTimers();
  expect(onClick).toHaveBeenCalledTimes(1);
  jest.useRealTimers();
});

What you did: Dummy component calls onClick prop after a delay when its button is clicked. I tried testing it using userEvent.click alongside jest.fakeTimers to avoid waiting for the delay.

What happened: The test onClick prop is called on button (userEvent, fakeTimer) fails due to timeout.

npx jest output

 FAIL  client/src/dummy/Dummy.test.js (11.923 s)
  ✕ onClick prop is called on button (userEvent, fakeTimer) (5014 ms)

  ● onClick prop is called on button (userEvent, fakeTimer)

    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."

      19 |
      20 | // Fails
    > 21 | test.only("onClick prop is called on button (userEvent, fakeTimer)", async () => {
         |      ^
      22 |   jest.useFakeTimers();
      23 |   const user = userEvent.setup();
      24 |   const onClick = jest.fn();

      at Object.<anonymous> (client/src/dummy/Dummy.test.js:21:6)

Reproduction repository: https://codesandbox.io/s/user-event-fake-timers-lwb65 (Source files are provided but I couldn’t get the tests to run on codesandbox)

Problem description:

The test fails due to timeout. The problem arises when using jest.useFakeTimers. The line await user.click(button) never completes.

Suggested solution:

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 9
  • Comments: 17 (4 by maintainers)

Commits related to this issue

Most upvoted comments

I wound up here looking for an answer to this problem. It seems to me that a better solution than described above is to make use of userEvent.setup({ advanceTimers: jest.advanceTimersByTime }): instead of disabling the delay entirely, use the provided mechanism to advance the fake timers when needed.

The implementation waits for delay seconds per setTimeout between actions. When fake timers are activated, new Promise(r => setTimeout(r, delay)) only resolves when the timer is advanced.

As delaying to the next macrotask is default behavior in v14, maybe #585 is more pressing now. Maybe we should add a paragraph about fake timers in a more prominent place in the documentation - maybe in the introduction or in an extra FAQ section.

For now you can disable delaying the next action per userEvent.setup({delay: null}).

Edit: fixed link

userEvent.setup({delay: null}) solved the problem. Thank you!

By the way, the documentation link wasn’t working. Maybe you meant this one: delay.

@sschneider-ihre-pvs this worked for me, could you verify that this also works for you please.

const user = userEvent.setup({delay:null});
// use the `user` variable instead of `userEvent`
user.type(element, 'text');

the above (also as vi. advanceTimersByTime) didn’t work for vitest, but this worked for me vi.useFakeTimers({ shouldAdvanceTime: true });

userEvent.setup({ advanceTimers: jest.advanceTimersByTime })

@s10mcow The documentation for advanceTimers is here: https://testing-library.com/docs/user-event/options#advancetimers The documentation for jest.advanceTimersByTime is here: https://jestjs.io/docs/timer-mocks#advance-timers-by-time

The implementation waits for delay seconds per setTimeout between actions. When fake timers are activated, new Promise(r => setTimeout(r, delay)) only resolves when the timer is advanced.

As delaying to the next macrotask is default behavior in v14, maybe #585 is more pressing now. Maybe we should add a paragraph about fake timers in a more prominent place in the documentation - maybe in the introduction or in an extra FAQ section.

For now you can disable delaying the next action per userEvent.setup({delay: null}).

Edit: fixed link

setting delay to null does not work for me, the test still does not complete in within the timeout bounds with .type

Has anyone come across the vitest equivalent of jest.advanceTimersByTime? I’m struggling to find much help from the docs

userEvent.setup({ advanceTimers: jest.advanceTimersByTime })

@s10mcow The documentation for advanceTimers is here: https://testing-library.com/docs/user-event/options#advancetimers The documentation for jest.advanceTimersByTime is here: https://jestjs.io/docs/timer-mocks#advance-timers-by-time

I think it might be worth mentioning it in https://testing-library.com/docs/using-fake-timers/