Skip to content
Advertisement

Preventing “not wrapped in act(…)” Jest warning when state update doesn’t affect UI

I’m trying to figure out if there is a way to prevent the “not wrapped in act(…)” warning thrown by Jest/testing-library when I have nothing to assert after the state update that causes the warning happens, or if I should just ignore this warning.

Suppose I have this simple component:

import React, {useEffect, useState} from 'react';
import {getData} from 'services';

const MyComponent = () => {
  const [arr, setArr] = useState([]);

  useEffect(() => {
    (async () => {
      const {items} = await getData();
      setArr(items);
    })();
  }, []);

  return (
    <div>
      {!(arr.length > 0) && <p>no array items</p>}
      {arr.length > 0 && (
        <ul>
          {arr.map(item => (
            <li key={item.id}>{item.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
};

export default MyComponent;

Suppose I want to simply test that this component renders okay even if getData() doesn’t return any data for me.

So I have a test like this:

import React from 'react';
import {getData} from 'services';
import {render, screen} from 'testUtils';
import MyComponent from './MyComponent';

jest.mock('services', () => ({
  getData: jest.fn(),
}));

it('renders', () => {
  getData.mockResolvedValue({items: []});

  render(<MyComponent />);

  expect(screen.getByText('no array items')).toBeInTheDocument();
});

This test will pass, but I’ll get the “not wrapped in act(…)” warning because the test will finish before getData() has a chance to finish.

In this case, the response from getData() sets arr to the same value (an empty array) as I have initially set it to at the top of the component. As such, my UI doesn’t change after the async function completes—I’m still just looking at a paragraph that says “no array items”—so I don’t really have anything I can assert that would wait for the state update to complete.

I can expect(getData).toHaveBeenCalledTimes(1), but that doesn’t wait for the state to actually be updated after the function call.

I have attempted an arbitrary pause in the test to allow time for setArr(items) to happen:

it('renders', async () => {
  getData.mockResolvedValue({items: []});

  render(<MyComponent />);

  expect(screen.getByText('no array items')).toBeInTheDocument();
  
  await new Promise(resolve => setTimeout(resolve, 2000));

  expect(screen.getByText('no array items')).toBeInTheDocument();
});

But that doesn’t seem to help, and I’m honestly not sure why.

Is there a way to handle this situation by modifying only the test?

I am sure I could fix the problem by refactoring MyComponent, e.g., by passing arr to MyComponent as a prop and moving the getData() call to a parent component, or creating some custom prop used only for testing that would skip the getData() call altogether, but I don’t want to be modifying components purely to avoid warnings in tests.

I am using testing-library/react, v11.2.2.

Advertisement

Answer

You can use findByText (a combination of getByText and waitFor) to ensure all updates have happened when the assertion resolves.

it('renders', async () => {
    getData.mockResolvedValue({items: []});
    render(<MyComponent />);
    expect(await screen.findByText('no array items')).toBeInTheDocument();
});
User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement