react-testing-library: Update to v14 breaks @testing-library/user-event on Vitest

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: https://github.com/wojtekmaj/react-async-button/commit/fa41b3b9900a25d76141bcf2080f94f7ee5f5dee

Suggested solution:

After long debug session, I have determined that

  • Monkey patching asyncWrapper to be just cb => cb() resolves the issue.
  • Removing the following code added in testing-library/react-testing-library#1137 resolves the issue:

https://github.com/testing-library/react-testing-library/blob/f78839bf4147a777a823e33a429bcf5de9562f9e/src/pure.js#L41-L52

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.

About this issue

  • Original URL
  • State: open
  • Created a year ago
  • Reactions: 18
  • Comments: 28 (8 by maintainers)

Commits related to this issue

Most upvoted comments

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);
  });
})

Supporting one framework’s fake timers is one thing, making the library framework-dependent by breaking it for all other testing frameworks is another. This code should NOT be run on non-Jest environments, and it does. Please see my original post.

Supporting Vitest timers should be as easy as calling vi instead of jest, and historically we’ve had messier code supporting differences in DOM implementations, so I think it’s worth trying. If anyone’s interested I can experiment with a PR.

So basically I’ve no idea why react-testing-library just sticks with Jest

Because it is the most popular testing framework so supporting their fake timers out of the box made sense to help adoption.

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.

Supporting Vitest timers should be as easy as calling vi instead of jest, and historically we’ve had messier code supporting differences in DOM implementations, so I think it’s worth trying. If anyone’s interested I can experiment with a PR.

It would be great to be able to pass your framework in the configure function. Something like:

import {configure} from '@testing-library/dom'
import {vi} from 'vitest'

// ...

configure({framework: vi})

The object passed through would adhere to a specific interface.

Even if the code has to be messy right now, you will eventually need something that’s much more agnostic. Jest won’t remain the dominant framework forever.

I found that userEvent can work nicely with vitest fake timers using:

    const user = userEvent.setup({
      advanceTimers: ms => vi.advanceTimersByTime(ms),
    });

~But I didn’t find a way to make waitFor work correctly.~

Edit, I take that back, I was still using v13 it turns out. With v14, the suggested approach above does seem to work for waitFor;

    globalThis.jest = {
      ...globalThis.jest,
      advanceTimersByTime: vi.advanceTimersByTime.bind(vi)
    };

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?

From what I can see here, react-testing-library is hard-coded to use jest.advanceTimersByTime after upgrading to v14 Current code: https://github.com/testing-library/react-testing-library/blob/main/src/pure.js#L49-L51 Corresponding PR: https://github.com/testing-library/react-testing-library/commit/f78839bf4147a777a823e33a429bcf5de9562f9e#diff-2ef28f1bd92d5dcd1f2a04d56814d3adaee10cc939b4a7d7c861af3a3cbbccb7 It’s not working like user-event where user is allowed to pass their advanceTimersByTime functions, like documented here: https://testing-library.com/docs/user-event/options#advancetimers

According to this, my workaround above will work 100% perfectly just by binding jest.advanceTimersByTime to vi.advanceTimersByTime, with binding of this to vi.

So basically I’ve no idea why react-testing-library just sticks with Jest, which is not making sense to me, but as long as I got a stable workaround there so it’s all goof for me.

But whatever, thanks for the great testing library!

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

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

Same problem

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

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