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

3

7

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.

Willie answered 9/5, 2020 at 10:20 Comment(0)
S
14

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.

Silviasilviculture answered 11/5, 2020 at 13:20 Comment(2)
Thanks, adding await act(async () => {}) right after the render call helped me get rid of the error.Willie
This works, but strangely I now get this warning: Warning: You called act(async () => ...) without await. Despite what the warning says, I've definitely waited the act()Jackstraws
C
2

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.

Contingency answered 16/9, 2020 at 21:16 Comment(1)
Exactly at which line do we need to add this? After act or create or expect?Tacet
I
0

I was only able to get this button go away by putting the expect and render calls in the same function. Returning the render result from a shared function caused the ForwardRef error. Very strange but I narrowed it down to this.

I'm also using a call to this wait() function after render, which lets Apollo run a GraphQL query too:

export async function wait(ms = 0): Promise<void> {
  return await act(async () => {
    return new Promise(resolve => {
      setTimeout(resolve, ms);
    });
  });
}
Inopportune answered 10/3, 2021 at 9:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.