I'm using react-native-testing-library
and after upgrading react-navigation
from 4 to 5, I followed these instructions: https://callstack.github.io/react-native-testing-library/docs/react-navigation to upgrade most of my test suite.
So far so good. The crux here is basically to wrap your tests in a NavigationContainer
so my components have access to those hooks that previously came from react-navigation-hooks
.
This works fine when my tests are synchronous, but as soon as I add the async
keyword to the test function, I get the following warning:
console.error
Warning: An update to ForwardRef(NavigationContainer) inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/docs/test-utils.html#act
in ForwardRef(NavigationContainer)
There are plenty of tests that I run synchronously and which succeed. But in some components, I do run some async logic to get the desired result.
From what I understand, I should wrap any async code in an act
call. However, even in this case, I can't get rid of this error.
I went ahead to try and narrow down the issue. Now, all I do is render the wrapped component like this:
test('a simple test', async () => {
const component = (
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
);
const {queryByText} = render(component);
expect(queryByText('test')).toBeNull();
});
And I'm still running into the same issue. Be aware that the actual test succeeds, only that I'm still getting this console error.
Next, I tried to wrap the render
call in act
and waitForElement
, because that's what I'm supposed to do when running async logic. But it seems that the async code here is executed after the rendering happens.
So I went on to investigate which part of NavigationContainer
is responsible for firing the async logic responsible for the error, and it seems that this bit of code (line 49-51) does something interesting:
const [isReady, initialState = rest.initialState] = useThenable(
getInitialState
);
getInitialState
is the result of destructuring the return value of the useLinking
call some lines before (43-47). Without going into further detail, getInitialState
is wrapped inside a promise in there, so it becomes "thenable".
Now, if I uncomment the useThenable
, the console error goes away. But obviously that's not something I can easily achieve from outside this file. So I'm a bit stuck here because I don't know how to write my test code in an async way without running into this error all the time, and I don't feel like ignoring or suppressing it is a good idea either.
Any help would be appreciated.
When testing React components with async state changes, like when data fetching with useEffect, you might get this error: Warning: An update to <SomeComponent> inside a test was not wrapped in act (...). When testing, code that causes React state updates should be wrapped into act (...)
In React Navigation 5, all of the configuration happens inside a component and is dynamic. This means we have access to props, state and context, and can dynamically change the configuration for the navigator!
When testing a screen that React Navigation renders you may need to render the navigation and the route properties. You can learn what properties you may need to mock by reading the docs: Next up is testing a navigator itself. An example of why you would want to do this is ensuring that the right screen is being shown given the state.
Couldn’t find a navigation object. Is your component inside a screen in a navigator? This happens because the useNavigation hook needs to retrieve the navigation prop from a parent Screen component, and you’re most likely rendering the component in isolation.
My recommendation after calling render
from react-native-testing-library
for components that do async work, useEffect
(acting as componentDidMount
), changing state, etc:
await act(async () => {})
Or even:
await act(async () => await flushMicrotasksQueue())
If you have some timeouts around.
The instance of Test Renderer has the unmount() method that unmount the in-memory tree, triggering the appropriate lifecycle events. At the end of your test add:
component.unmount()
You can get more information at https://reactjs.org/docs/test-renderer.html#testrendererunmount.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With