0

I'm facing a problem unit-testing a component with react-native-testing-library.

I have a component like this:

// components/TestComponent.js
function TestComponent() {
  const [data, setData] = useState();

  useEffect(() => {
    clientLibrary.getData()
      .then((result) => { setData(result.data); } )
      .catch((err) => { //handle error here } )
  }, []);

  render (
    <ListComponent 
      testID={"comp"} 
      data={data})
      renderItem={(item) => <ListItem testID={'item'} data={item} />}
    />
  );
}

And I test it like this:

// components/TestComponent.test.js

it('should render 10 list item', async () => {
  const data = new Array(10).fill({}).map((v, idx) => ({
      id: `v_${idx}`,
    }));

  const req = jest.spyOn(clientLibrary, 'getData').mockImplementation(() => {
      return Promise.resolve(data);
    });

  const {queryByTestId, queryAllByTestId} = render(
    <TestComponent />,
  );

  expect(await queryByTestId('comp')).toBeTruthy(); // this will pass
  expect(await queryAllByTestId('item').length).toEqual(10); // this will fail with result: 0 expected: 10
}); // this failed

The test will fail/pass with

Attempted to log "Warning: An update to TestComponent inside a test was not wrapped in act(...). pointing to setData in useEffect.

I've tried wrapping the render with act(), the assertion with act(), not mocking the api call, wrapping the whole test in act(), but the error won't go away.

I have tried looking at testing-library docs/git/q&a for this case, scoured stackoverflow questions too, but I still can't make this test works.

Can anyone point me to the right direction to solve this?

A note: I'm not trying to test implementation detail. I just want to test that given a fetch result X, the component would render as expected, which is rendering 10 list item.

  • You should wait and assert that whatever gets rendered in your `ListComponent` using `data` is present - that'll ensure the logic inside your `useEffect` has ran. – juliomalves Sep 11 '21 at 18:18
  • thanks for the suggestion @juliomalves. i think I should correct my phrasing in my question. The test do pass if I check certain things, but it still complain with the `not wrapped in act` warning pointing to the setState inside the useEffect, and since it was print in red my brain just think of it as failing since that means I'm not doing something right even if it pass. Is it safe to ignore the warning if the test pass? The warning doesn't exactly promote confidence in my test... – gretchelin Sep 12 '21 at 06:17
  • This seems to be two separate issues: (1) using `waitFor` or `findBy` to wait for the async task to resolve, (2) dealing with the `act` warning. See [how to test useEffect with act](https://stackoverflow.com/questions/59131116/react-native-testing-library-how-to-test-useeffect-with-act) and [React Native testing - act without await](https://stackoverflow.com/questions/64952449/react-native-testing-act-without-await) respectively. – ggorlen Nov 07 '21 at 05:49

1 Answers1

1

Your component is performing an asynchronous state update during mounting inside useEffect so the act of rendering has an asynchronous side effect that needs to be wrapped in an await act(async()) call. See the testing recipes documentation on data fetching.

You can try something like this in your test:

it('should render 10 list item', async () => {
  // Get these from `screen` now instead of `render`
  const { queryByTestId, queryAllByTestId } = screen
  const data = new Array(10).fill({}).map((v, idx) => ({
      id: `v_${idx}`,
    }));

  const req = jest.spyOn(clientLibrary, 'getData').mockImplementation(() => {
      return Promise.resolve(data);
    });

  await act(async () => {
    render(
      <TestComponent />
    );
  })

  expect(await queryByTestId('comp')).toBeTruthy();
  expect(await queryAllByTestId('item').length).toEqual(10);
});

morganney
  • 6,566
  • 1
  • 24
  • 35