testing-library / dom-testing-library

🐙 Simple and complete DOM testing utilities that encourage good testing practices.
https://testing-library.com/dom
MIT License
3.27k stars 467 forks source link

waitFor fake timers loop prevents event loop from running #1072

Closed mc0 closed 2 years ago

mc0 commented 3 years ago

Relevant code or config:

See reproduction

What you did:

With modern fake timers in Jest: Attempting to use latest Jest, MSW, and react-testing-library to run a test that fetches data from an API and validates a component.

What happened:

Responses are delayed until after the test runs as MSW uses NodeJS timers[0] and dom-testing-library blocks the event loop while using waitFor.

0: https://github.com/mswjs/msw/blob/6decc6015e59c1b1aa3706c9bda467e2e3750c79/rollup.config.ts#L134

Reproduction:

https://codesandbox.io/s/react-testing-library-msw-demo-c7nd2

May need to be forked to run

Problem description:

Advancement of time via dom-testing-library is super handy, but has compatibility problems with other libraries.

Suggested solution:

We need some way of engaging with the event loop. This is tricky since there is no supported way of getting the original timer functions from Jest.

Option 1: Config

To properly support waitFor when using fake timers, the code must provide the original setTimeout/clearTimeout via configuration.

If configured: Use setTimeout(..., 0), use advanceTimerByTime to progress the fake timers, and do our checks until the timeout occurs using the fake timer.

Option 2: postMessage for browser, timers for NodeJS

The fake timers don't overwrite postMessage, and this is an entrypoint to the JS event loop. In the browser, we can add a listener for a message and then use this to advance the timer and do our checks. (Edit: It's possible handling this in a browser isn't necessary as there are fewer ways around the fake timers, but I left this in for completeness.)

For NodeJS: Use timers.setTimeout(..., 0), use advanceTimerByTime to progress the fake timers, and do our checks until the timeout occurs using the fake timer.

Option 3: Disable and enable for original timer functions

The first time waitFor is ran while fake timers are enabled, the fake timers can be disabled, the functions can be stored, and then re-enabled.

i.e.

idanen commented 2 years ago

Thanks for raising this. I found myself chasing what goes wrong with my tests and it was really hard! Ended up using sinon's fake-timers with config. So now I'm actually scared of using jest.useFakeTimers() since it mocks everything which makes it really hard to find what's wrong.

Personally I prefer adding config to the waitFor to allow users to opt-in on this event loop manipulation.

eps1lon commented 2 years ago

Duplicate of https://github.com/testing-library/dom-testing-library/issues/988

If msw is still ignoring the clock the test wants to run in, then there's little we can do.