Skip to content

Update to v14 breaks @testing-library/user-event on Vitest #1197

Description

@wojtekmaj

What you did:

A simple update from v13 to v14 broke my Vitest-based test where I was using await user.click(...) as the promise no longer resolves.

Reproduction:

Run repo at the following commit: wojtekmaj/react-async-button@fa41b3b

Suggested solution:

After long debug session, I have determined that

// Drain microtask queue.
// Otherwise we'll restore the previous act() environment, before we resolve the `waitFor` call.
// The caller would have no chance to wrap the in-flight Promises in `act()`
await new Promise(resolve => {
setTimeout(() => {
resolve()
}, 0)
if (jestFakeTimersAreEnabled()) {
jest.advanceTimersByTime(0)
}
})

  • Moving if (jestFakeTimersAreEnabled()) { ... } to wrap the entire block mentioned above resolves the issue.
  • Calling vi.advanceTimersByTime(0); manually after user.click(...) but before awaiting returned promise, even multiple times, does NOT help
  • The only workaround that worked for me was this: wojtekmaj/react-async-button@2d26f21

So my suggestion is to:

  • Roll back the fix and perhaps reintroduce when advanceTimers will be configurable and not jest dependent
  • OR move if (jestFakeTimersAreEnabled()) { ... } to wrap the entire block mentioned above, acknowledging that the fix is now Jest-only.

Activity

  1. eps1lon commented on Mar 25, 2023

    @eps1lon
    Member

    Moving to user-event until we have a repro that's just using @testing-library/react

  2. Lokua commented on Mar 28, 2023

    @Lokua

    I was about to submit what I think is the same issue. await userEvent.click causes any test that is ran after calling jest.useFakeTimers to fail in v14.4.3. Took me a pretty long time to isolate it, but it appears to be the combination of using fake timers and awaiting userEvent, as remove either of those things and the tests pass.

    Here is a repro using @testing-library/react:
    https://github.com/Lokua/user-event-repro

    ^ downgrading to v13 or using fireEvent, no issue.

    Happy to create a separate issue if you feel this is not the same.

    Edit: If this is the same issue, I suggest renaming it to better reflect the actual bug so others can find it more easily. Perhaps v14 causes tests using fake timers to fail when awaiting userEvent.click

  3. ph-fritsche commented on Mar 29, 2023

    @ph-fritsche
    Member

    @Lokua You need to set the advanceTimers option if you're working with fake timers.

    We recommend using a setup function:

    function setup(jsx) {
      return {
        user: userEvent.setup({
          advanceTimers: jest.advanceTimersByTime,
        }),
        ...render(jsx),
      }
    }
    
    test('some click', async () => {
      jest.useFakeTimers()
      const { user } = setup(<button/>)
    
      await user.click(screen.getByRole('button'))
    })
  4. ph-fritsche commented on Mar 29, 2023

    @ph-fritsche
    Member

    @wojtekmaj @eps1lon

    This is an issue with the setTimeout call in asyncWrapper and is independent of user-event. It just happens to use it.

    When we use setTimeout in user-event we also call the advanceTimers callback.
    I think we should add the same option to @testing-library/react.

    import DTL from '@testing-library/dom'
    const userEventConfig = {
      delay: 0,
      advanceTimers: jest.advanceTimersByTime,
    }
    const userEventWait = () => Promise.all([
       new Promise(r => setTimeout(r, userEventConfig.delay)),
       userEventConfig.advanceTimers(userEventConfig.delay),
    ])
    const someUserEventApi = () => {
      return DTL.getConfig().asyncWrapper(() => {
         DTL.getConfig().eventWrapper(() => document.dispatchEvent(new Event('foo')))
         DTL.getConfig().eventWrapper(() => document.dispatchEvent(new Event('bar')))
         await userEventWait()
         DTL.getConfig().eventWrapper(() => document.dispatchEvent(new Event('baz')))
         await userEventWait()
       })
    }
    
    await someUserEventApi() // with non-jest fake timers this will time out because of https://github.com/testing-library/react-testing-library/blob/f78839bf4147a777a823e33a429bcf5de9562f9e/src/pure.js#L44-L52
  5. Lokua commented on Mar 29, 2023

    @Lokua

    @Lokua You need to set the advanceTimers option if you're working with fake timers.

    We recommend using a setup function:

    function setup(jsx) {
      return {
        user: userEvent.setup({
          advanceTimers: jest.advanceTimersByTime,
        }),
        ...render(jsx),
      }
    }
    
    test('some click', async () => {
      jest.useFakeTimers()
      const { user } = render(<button/>)
    
      await user.click(screen.getByRole('button'))
    })

    Thank you very much for the info. I get it now. Just for clarity, in your example, setup should actually be called render, or vis a versa, yes?

  6. ph-fritsche commented on Mar 29, 2023

    @ph-fritsche
    Member

    @Lokua Yes, the setup function should be called in the test, I fixed the code example above.

  7. runofthemillgeek commented on Mar 31, 2023

    @runofthemillgeek

    @ph-fritsche I ran into the same issue but I couldn't get my scenario to work just with advanceTimers option. See repro link: https://stackblitz.com/edit/vitest-dev-vitest-2xdjiw?file=test%2Fcountdown.test.tsx

    import Countdown from "src/components/Countdown";
    import { render, screen, act } from "@testing-library/react";
    import userEvent from "@testing-library/user-event";
    
    describe("Mocking timers", () => {
      beforeEach(() => {
        vi.useFakeTimers();
      });
    
      afterEach(() => {
        vi.runOnlyPendingTimers();
        vi.useRealTimers();
      });
    
      it("mocks timers", async () => {
        const user = userEvent.setup({
          advanceTimers: ms => vi.advanceTimersByTime(ms),
        });
    
        render(<Countdown from={5} />);
    
        await user.click(screen.getByRole("button"));
    
        expect(screen.getByRole("alert")).toHaveTextContent("5");
        await act(() => vi.runAllTimers());
        expect(screen.getByRole("alert")).toHaveTextContent("0");
      });
    });

    If I do:

    vi.useFakeTimers({ shouldAdvanceTime: true });

    It makes the test pass but I believe it's not a good option to use.

  8. azat-io commented on Apr 6, 2023

    @azat-io

    Same problem

  9. bfsgr commented on Apr 11, 2023

    @bfsgr

    I believe this might be related to #1187 as vitest uses sinon fake timers

  10. xsjcTony commented on Apr 13, 2023

    @xsjcTony

    Is there any ETA to fix this issue? It should be testing-framework agnostic instead of sticking with Jest

    One temporarily workaround for Vitest users can be like this:

    In your test suites using fake timers

    import { beforeAll, vi, describe } from 'vitest';
    
    describe('this suite uses fake timers', () => {
      // Temporarily workaround for bug in @testing-library/react when use user-event with `vi.useFakeTimers()`
      beforeAll(() => {
        const _jest = globalThis.jest;
      
        globalThis.jest = {
          ...globalThis.jest,
          advanceTimersByTime: vi.advanceTimersByTime.bind(vi)
        };
      
        return () => void (globalThis.jest = _jest);
      });
    })
  11. added
    bugSomething isn't working
    and removed
    bugSomething isn't working
    on May 19, 2023
  12. 13 remaining items

  13. senock-dag commented on Sep 29, 2023

    @senock-dag

    Unfortunately the above solution didn't work for me. I've downgraded to v13.5.0 which has worked.

    Is a fix being worked on for future releases?

  14. xuhdev commented on Feb 15, 2024

    @xuhdev

    @IanVS 's method works for me. Should we add this to the doc?

  15. danielholmes commented on Apr 25, 2024

    @danielholmes

    The following worked for my case:

    vi.useFakeTimers({
          shouldAdvanceTime: true
    });
    
  16. robertjk commented on Apr 25, 2024

    @robertjk

    The following worked for my case:

    vi.useFakeTimers({
          shouldAdvanceTime: true
    });
    

    I don't think that's a real solution. What shouldAdvanceTime option does is advance fake timers with normal time, which kinds of defeat the purpose of using fake timers, and make your test flaky.

    So far I didn't find any solution which would work for Vitest.

  17. danielholmes commented on Apr 25, 2024

    @danielholmes

    I don't think that's a real solution. What shouldAdvanceTime option does is advance fake timers with normal time, which kinds of defeat the purpose of using fake timers, and make your test flaky.

    I agree it's not a real solution. But if you're dealing with dates in terms of days (my use case), weeks, months or years it's a perfectly fine workaround for now. If your use case needs the precision of milliseconds or seconds then it's not a good workaround.

  18. robertjk commented on Apr 27, 2024

    @robertjk

    I don't think that's a real solution. What shouldAdvanceTime option does is advance fake timers with normal time, which kinds of defeat the purpose of using fake timers, and make your test flaky.

    I agree it's not a real solution. But if you're dealing with dates in terms of days (my use case), weeks, months or years it's a perfectly fine workaround for now. If your use case needs the precision of milliseconds or seconds then it's not a good workaround.

    Fair enough - in some use cases it could be a valid solution.

  19. added 3 commits that reference this issue on Jan 16, 2025
  20. yaroslavvertukhov commented on Jan 28, 2025

    @yaroslavvertukhov

    Any news on solving this problem?

  21. yaroslavvertukhov commented on Jan 28, 2025

    @yaroslavvertukhov

    So far my problem has been solved with a crutch

    import { vi } from 'vitest'
    globalThis.jest = vi

  22. krutoo commented on Feb 4, 2025

    @krutoo

    when will this problem be solved in this testing-library/react?

  23. ardunster commented on Jun 26, 2025

    @ardunster

    Just wasted an hour or so fighting with this and had to swap to using fireEvent for the clicks in question, which I understand is discouraged. Would be great if this actually gets resolved.

  24. mihkeleidast commented on Oct 22, 2025

    @mihkeleidast

    I'd like to support more timers but so far no community contributions have been made to do that. And since I'm not using Vitest in any projects I'm involved in, I didn't have a use-case for myself. PRs are welcome though.

    @eps1lon could we discuss & agree on what the API for configuring this should look like, so the community could help with implementation? I did file a PR (#1402) with an initial idea back in June, but haven't received any response or feedback.

    Seems quite trivial to implement, I think mainly the question is whether it should be a single config function (advanceTimers), or with a separate check function (areFakeTimersEnabled or sth like that).

    Would love to get this sorted and willing to put in the work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions