Description
Hey, I get some of my tests timing out when using waitFor and jest.useFakeTimers, but not using a timer internally, but only Promise.resolve
. As a context I'm trying to migrate a bigger code base from v4 to the latest version from v5 on some tests are failing. I'm also using jests faketimers by default for the tests.
This might be a bit related to #631
react-hooks-testing-library
version: 8.0.1react
version: 17.02react-dom
version (if applicable): 17.02react-test-renderer
version (if applicable): 17.02node
version: 16.14.2yarn
version: 1.22.10
Relevant code or config:
const React = require('react');
const { renderHook } = require('@testing-library/react-hooks');
jest.useFakeTimers(); // modern or fake doesn't matter here
const useStateAndAsyncEffect = (fn) => {
const [s, set] = React.useState(false);
React.useEffect(() => {
Promise.resolve().then(() => set(true));
}, []);
React.useEffect(() => {
if (s) fn();
}, [s]);
return;
};
test('test hook', async () => {
const fn = jest.fn();
const { waitFor } = renderHook(() => useStateAndAsyncEffect(fn));
await waitFor(() => expect(fn).toHaveBeenCalled());
});
What happened:
The test fails from v5 and onwards, but worked in v4.
Suggested solution:
I was digging a bit into the code and saw v4 is calling act
inside async-utils inside the while(true)
loop, while from v5 upwards act
is only called once. I've tried to figure out the details, but not really sure why calling act more than once is making this work. I'm thinking about react flushing micro tasks more often, but also not very familiar with react internals/fibers.
I've played with patch-package on got this diff working for me. Please let me know what you think about it 🙏
diff --git a/node_modules/@testing-library/react-hooks/lib/core/asyncUtils.js b/node_modules/@testing-library/react-hooks/lib/core/asyncUtils.js
index dbf1a49..77b9785 100644
--- a/node_modules/@testing-library/react-hooks/lib/core/asyncUtils.js
+++ b/node_modules/@testing-library/react-hooks/lib/core/asyncUtils.js
@@ -26,9 +26,11 @@ function asyncUtils(act, addResolver) {
const waitForResult = async () => {
while (true) {
- const intervalSignal = (0, _createTimeoutController.createTimeoutController)(interval);
- timeoutSignal.onTimeout(() => intervalSignal.cancel());
- await intervalSignal.wrap(new Promise(addResolver));
+ await act(async () => {
+ const intervalSignal = (0, _createTimeoutController.createTimeoutController)(interval);
+ timeoutSignal.onTimeout(() => intervalSignal.cancel());
+ await intervalSignal.wrap(new Promise(addResolver));
+ })
if (checkResult() || timeoutSignal.timedOut) {
return;
@@ -37,7 +39,7 @@ function asyncUtils(act, addResolver) {
};
if (!checkResult()) {
- await act(() => timeoutSignal.wrap(waitForResult()));
+ await timeoutSignal.wrap(waitForResult());
}
return !timeoutSignal.timedOut;
Activity
eps1lon commentedon Jul 18, 2022
Probably another instance of #589. @mpeyper does
/react-hooks
manually flush the microtask queue when you're detecting fake timers? It looks like/react-hooks
doesn't.For comparison,
/react
manually flushes the microtask queue (although hacky) if we detect fake timers.@5c077yP Could you check if the test still times out when you use
?
5c077yP commentedon Jul 18, 2022
Hey @eps1lon , yes the test does work with
/react
out of the boxmpeyper commentedon Jul 18, 2022
No, we have never supported fake times. An attempt was made in a alpha build some time ago, but was shelved after the decision was made to move
renderHook
into/react
for react 18.eps1lon commentedon Jul 18, 2022
Yeah makes sense. I had some ideas for a simpler
waitFor
implementation in/dom
(which/react
) is using. I'll try to revisit them since that might enable us to usewaitFor
from/react
when using/react-hooks
i.e. makewaitForm
from/react-hooks
obsolete.lgibso34 commentedon Aug 29, 2022
Came here to report the same thing.
I fixed my issue by using the
waitFor
from@testing-library/react
. It checks for fake timers.