You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Test with suspending component times out with fakeTimers: { enableGlobally: true } #1347
I've set up a simple test with a suspending component (no timers, just a return in async / await) that eventually renders a text. The test waits for the text with await findByText.
With jest.useFakeTimers(), the test pass.
With fakeTimers: { enableGlobally: true } in jest.config.js, the test fails.
Expected behavior
The test should pass in both cases.
Steps to Reproduce
Clone the examples/basic directory from the repo
Add this test:
import * as React from 'react';
import { render, screen } from '@testing-library/react-native';
import { Text, View } from 'react-native';
beforeEach(() => {
jest.useFakeTimers();
// Logs a warning if modern fake timers are not set
jest.setSystemTime(Date.now());
});
test('should work', async () => {
let loading = true;
let data: string | undefined = undefined;
const Suspending = () => {
if (loading)
throw (async () => {
return await 'result';
})().then((result) => {
data = result;
loading = false;
});
return <Text>{data}</Text>;
};
render(
<View>
<React.Suspense fallback={<Text>Loading</Text>}>
<Suspending />
</React.Suspense>
</View>
);
await screen.findByText('result');
});
Run the test: it passes ✅
In jest.config.js, add the line fakeTimers: { enableGlobally: true }
Not sure how well RNTL handles React Suspense, I will have to spend some time to check it out.
BTW why are you throwing a function returning a promise? Also await 'result' seems to be unnecessary as awaiting non-promise value just returns it. Shouldn't the Suspending component throw a promise?
I've started to look into this and it's not easy. I haven't made much progress so far but I did a reproduction of the issue using nearly no code from RNTL:
I'm only using getQueriesForElement but I'm pretty sure it's not the source of this bug. That would mean that the problem lies elsewhere, although I'm not sure yet where, but I'd guess either jest or react-test-renderer
Also in both cases the promise resolves correctly but in one case we still see the loading state so it's definitely related to Suspense having a different behavior in both situations somehow
@robingullo, @pierrezimmermannbam, @matthieugicquel if I understand the above conclusions, seem to indicate that this is a Jest issue, and not a RNTL one. Do you agree with that conclussion?
@mdjastrzebski I found a similar issue on the react repo, with a deep analysis. It looks like a more general React / Jest incompatibility. The issue explains how it can fixed by switching to real timers before module imports:
jest.useRealTimers(); // fails with jest.useFakeTimers()
const { Suspense } = require('react');
const { render } = require('@testing-library/react-native');
const { Text, View } = require('react-native');
jest.useFakeTimers();
beforeEach(() => {
jest.useFakeTimers();
// Logs a warning if modern fake timers are not set
jest.setSystemTime(Date.now());
});
test('should work', async () => {
let loading = true;
let data: string | undefined = undefined;
const Suspending = () => {
if (loading)
throw Promise.resolve('result').then((result) => {
data = result;
loading = false;
});
return <Text>{data}</Text>;
};
const screen = render(
<View>
<Suspense fallback={<Text>Loading</Text>}>
<Suspending />
</Suspense>
</View>,
);
await screen.findByText('result');
});
I've had to do the same doNotFake: ["setImmediate"] config change, even when not using any suspense feature though. And that was only needed when enabling globally, but not locally. But when I tried that on the example repo, I couldn't reproduce.
Activity
mdjastrzebski commentedon Feb 24, 2023
Not sure how well RNTL handles React Suspense, I will have to spend some time to check it out.
BTW why are you throwing a function returning a promise? Also
await 'result'seems to be unnecessary as awaiting non-promise value just returns it. Shouldn't theSuspendingcomponent throw a promise?robingullo commentedon Feb 27, 2023
I think it throws a promise, the arrow function is called immediately. I admit it might not be the clearest syntax.
robingullo commentedon Feb 27, 2023
I encountered the issue initally with React Query btw
pierrezimmermannbam commentedon Feb 28, 2023
I've started to look into this and it's not easy. I haven't made much progress so far but I did a reproduction of the issue using nearly no code from RNTL:
I'm only using getQueriesForElement but I'm pretty sure it's not the source of this bug. That would mean that the problem lies elsewhere, although I'm not sure yet where, but I'd guess either jest or react-test-renderer
Also in both cases the promise resolves correctly but in one case we still see the loading state so it's definitely related to Suspense having a different behavior in both situations somehow
matthieugicquel commentedon Mar 16, 2023
I've noticed that issue is linked to the faking of
setImmediateonly. With this config, the test passes:mdjastrzebski commentedon Mar 16, 2023
@robingullo, @pierrezimmermannbam, @matthieugicquel if I understand the above conclusions, seem to indicate that this is a Jest issue, and not a RNTL one. Do you agree with that conclussion?
robingullo commentedon Mar 16, 2023
@mdjastrzebski I found a similar issue on the react repo, with a deep analysis. It looks like a more general React / Jest incompatibility. The issue explains how it can fixed by switching to real timers before module imports:
But of course it works only with require...
AugustinLF commentedon Mar 16, 2023
I've had to do the same
doNotFake: ["setImmediate"]config change, even when not using any suspense feature though. And that was only needed when enabling globally, but not locally. But when I tried that on the example repo, I couldn't reproduce.mdjastrzebski commentedon Jul 21, 2023
Closing as both stale and non-actionable at the moment. There is known workaround (Use
jest.useFakeTimers()instead of{ enableGlobally: true }.