Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async testing React Navigation 5 in Jest: NavigationContainer causes console error

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.

like image 300
Marcel Kalveram Avatar asked May 09 '20 10:05

Marcel Kalveram


People also ask

When testing React components with async state changes I get This error?

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 (...)

What's new in react navigation 5?

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!

How do I test a react 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.

Why could not find a navigation object in a component?

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.


2 Answers

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.

like image 153
Ferran Negre Avatar answered Oct 19 '22 22:10

Ferran Negre


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.

like image 28
Diego Mais Avatar answered Oct 19 '22 20:10

Diego Mais