Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

userEvent.click fails due to timeout when used with jest.useFakeTimers #833

Closed
fabrizzio-gz opened this issue Jan 15, 2022 · 17 comments
Closed

Comments

@fabrizzio-gz
Copy link

  • @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:

@ph-fritsche
Copy link
Member

ph-fritsche commented Jan 15, 2022

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

@fabrizzio-gz
Copy link
Author

fabrizzio-gz commented Jan 16, 2022

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
Copy link

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

@ph-fritsche
Copy link
Member

@sschneider-ihre-pvs Please provide more information. If delay: null didn't work for you, your problem is probably unrelated to this issue.

@timdeschryver
Copy link
Member

@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');

robinmetral pushed a commit to sumup-oss/circuit-ui that referenced this issue Jun 9, 2022
- awaited userEvent methods and removed unnecessary act blocks
- migrated useSidePanel to newRenderHook and removed unnecessary actHook blocks
- added ariaHideApp=false to default test props in SidePanelContext to silence react-modal warning about the undefined app element
- set up userEvent with delay=null in SidePanelContext to address this issue: testing-library/user-event#833
- TODO: two tests using userEvent.keyboard() are still failing. Marked as todo.
- TODO: there are two act() warnings left (likely from before) when running `yarn test sidepanel` (all SidePanel-related specs)
robinmetral pushed a commit to sumup-oss/circuit-ui that referenced this issue Jun 13, 2022
- awaited userEvent methods and removed unnecessary act blocks
- migrated useSidePanel to newRenderHook and removed unnecessary actHook blocks
- added ariaHideApp=false to default test props in SidePanelContext to silence react-modal warning about the undefined app element
- set up userEvent with delay=null in SidePanelContext to address this issue: testing-library/user-event#833
- TODO: two tests using userEvent.keyboard() are still failing. Marked as todo.
- TODO: there are two act() warnings left (likely from before) when running `yarn test sidepanel` (all SidePanel-related specs)
robinmetral pushed a commit to sumup-oss/circuit-ui that referenced this issue Jun 14, 2022
* Upgrade user-event to v14

* Address breaking changes in Popover spec

- removed the mocked document.createRange. This isn't necessary anymore. See https://github.com/testing-library/user-event/issues/902\#issuecomment-1092886633
- awaited userEvent calls, removed unnecessary act blocks
- fixed type errors around icon types
- note: there are still act warnings for some tests in the specs, even some not involving any state updates (style snapshots). Probably points to unwanted rerenders. Added a fixme comment.

* Address breaking changes in useEscapeKey

- exposed the renderHook method from @testing-library/react, meant to replace the onoe from @testing-library/react-hooks, which isn't compatible with React 18. For migration purposes, I'm naming it newRenderHook--I'll remove the legacy method and rename the new one after all specs have been migrated
- awaited userEvent calls and removed unnecessary act blocks

* Fix accessibility error in ToastContext

The live region wrapping toasts should not be a ul, because uls son't allow the status role (likely because it overrides list semantics anyways. Switched for divs.

Found through a better axe test

* Migrate user-event in NotificationToast spec

- await userEvent method calls
- added a new render helper to render a component tree including the ToastProvider, to reduce test boilerplate when testing business logic
- improved the accesibility test to cover an actual document with a toast instead of the toast UI in isolation

* WIP: upgrade user-event in SidePanel components

- awaited userEvent methods and removed unnecessary act blocks
- migrated useSidePanel to newRenderHook and removed unnecessary actHook blocks
- added ariaHideApp=false to default test props in SidePanelContext to silence react-modal warning about the undefined app element
- set up userEvent with delay=null in SidePanelContext to address this issue: testing-library/user-event#833
- TODO: two tests using userEvent.keyboard() are still failing. Marked as todo.
- TODO: there are two act() warnings left (likely from before) when running `yarn test sidepanel` (all SidePanel-related specs)

* Patch keyCode event in SidePanel spec

This also adds comments on accessibility tests that inconsistently trigger axe warnings. Will be investigated and addressed separately.

* Migrate Button, RadioButton, RadioButtonGroup, Header

Awaited userEvent calls.

* Migrate remaining input components

Checkbox, CurrencyInput, Selector.

Awaited userEvent method calls

* Migrate notification components

Awaited userEvent method calls

* Migrate modal components

- awaited userEvent method calls
- set up userEvent with delay=null in ModalContext spec (same as in SidePanelContext)

* Migrate more components

Hamburger, Pagination, PageList, UtilityLinks, useClickOutside.

Awaited userEvent method calls.

Does not include the migration to new renderHook for useClickOutside, will be handled separately.

* Use userEvent with delay=null in ToastProvider spec

Same as in the ModalProvider and SidePanelProvider

* Migrate userEvent in useAutoExpand

- awaited method calls
- migrated {space} to { }
- TODO: migrate to the new renderHook, will be addressed separately

* Migrate userEvent in remaining components

- await method calls
- fix some TS errors

* Migrate useStep to renderHook from RTL

- replace imports
- replace removed waitForNextUpdate by a more explicit waitFor
- remove tests covering error logic. Error testing was removed in the renderHook port to RTL and we're not covering this in other components, so I think it's fine to remove.

See testing-library/react-testing-library#991 for more context

* Migrate renderHook from RHTL to new RTL implementation

See testing-library/react-testing-library#991 for context.

- replaced import of renderHook to newRenderHook (will be renamed in a follow-up commit)
- replaced actHook by act
- (edge cases were handled in earlier commits)

* Rename newRenderHook to renderHook

* Await remaining userEvent promises

Not sure why these specs passed locally before

* Address review comment
@anomiex
Copy link

anomiex commented Jun 30, 2022

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.

@ph-fritsche
Copy link
Member

You're correct. The information above is outdated.
advanceTimers was introduced in v14.1.0 on 2022-04-11 😃

@s10mcow
Copy link

s10mcow commented Oct 19, 2022

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.

Where is the documentation for this located?

@clarkedb
Copy link

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

@remy90
Copy link

remy90 commented Dec 21, 2022

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

@leonadler
Copy link

@remy90 the equivalent would be vitest.advanceTimersByTime.
No idea how to set this up "globally" in a setupTests script, however.

@dbie999
Copy link

dbie999 commented Sep 19, 2023

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

@nbolton
Copy link

nbolton commented Oct 22, 2023

No idea how to set this up "globally" in a setupTests script, however.

If you want to use this globally, you could add the following to setupTests.js:

import userEvent from "@testing-library/user-event";
global.user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });

@TrueWill
Copy link

TrueWill commented Nov 7, 2023

None of these solutions work for me.

fireEvent.change(screen.getByRole('textbox', { name: /slug/i }), { target: { value: 'test' } });

works (with subsequent assertions), but

const user = userEvent.setup({ delay: null }); // same for { advanceTimers: jest.advanceTimersByTime }

// render here

await user.type(screen.getByRole('textbox', { name: /slug/i }), 'test');

gives pages of act() warnings (hiding the actual error) and fails the test.

Using v14.5.1.

I am using jest.useFakeTimers().

@gusa4grr
Copy link

@TrueWill
this will fix act warnings at least :)
await act(() => user.type(screen.getByRole('textbox', { name: /slug/i }), 'test'))

@vladern
Copy link

vladern commented Nov 29, 2023

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.

Yes this worked for me Thanks :)

@oncet
Copy link

oncet commented Jan 12, 2024

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/

github-merge-queue bot pushed a commit to dfinity/nns-dapp that referenced this issue Mar 8, 2024
# Motivation

While cleaning up a test in another branch, I discovered that
`userEvent.selectOption` doesn't work when fake timers are enabled.

I found a work-around here:
testing-library/user-event#833
```
userEvent.setup({ advanceTimers: jest.advanceTimersByTime })
```

It also seems that `vitest` does not expose a way to test if fake timers
are enabled, but fake timers specific method throw if fake timers are
not enabled so this can be used to detect if fake timers are enabled.

# Changes

1. Add a function `areFakeTimersEnabled` to check if fake timers are
enabled.
2. If fake timers are enabled, use the work-around to use
`userEvent.selectOption` in `JestPageObjectElement.selectOption`.

# Tests

Used in another branch to stop a test from timing out after enabling
fake timers.

# Todos

- [x] Add entry to changelog (if necessary).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests