When testing, code that causes React state updates should be wrapped into act(...)

Viewed 618

I am testing my component

ForecastButtons.js

export const ForecastButtons = ({ city }) => {
  const [payload, setPayload] = useState(null)

  const getData = () => {
    fetchCityData(city).then((payload) => setPayload(payload));
  }
  const location = payload?.location?.name;
  const currentTemp = payload?.current?.temp_c;

  return(
    <div className="sm:col-span-2">
      <p className="block text-sm font-medium text-gray-700">Select forecast</p>
        <button onClick={getData} className="mt-1 bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded" type='button'>
          Today
        </button>
        <p key={city?.location?.id} className='my-5'>
          { location ? `Current weather in ${location} is ${currentTemp} degrees ` : 'Please search for city to see current weather'}
        </p>
    </div>
  )
}

This is the part of my test:

    test('render weather into component',  async () => {
    
      const { getByText } = render(<ForecastButtons weather={weatherResponce} city={'London'} />);
      const button = getByText('Today')
    
      await act(async () => {
        await fireEvent.click(button)
      })
      expect(getByText('London')).toBeInTheDocument();
    })

Please be aware, it's not the whole test, just the part, which is not working. The error is above. As you can see, I added the act() to it, but it still keeps throwing the error: When testing, code that causes React state updates should be wrapped into act(...)

1 Answers

I could find some hint in React Testing Library and the “not wrapped in act” Errors on Medium, where are a lot of cases are explained very well.

First useful learning:

React testing library already integrated act with its APIs. So in most cases, we do not need to wrap render and fireEvent in act. For example:

// With react-testing-library
it("should render and update a counter", () => {
  // Render a component
  const { getByText } = render(<Counter />;
  ...  

  // Fire event to trigger component update
  fireEvent.click(getByText("Save"));
  ...
});

In my case I got the error (my assumption as a beginner), because fireEvent.click triggers fetchData to be called, which is an asynchronous call. When its response comes back, fetchCityData/getData will be invoked, but at this moment, the update will happen outside of React’s call stack.

Solution

Before assertions, wait for component update to fully complete by using waitFor. waitFor is an API provided by React Testing Library to wait for the wrapped assertions to pass within a certain timeout window.

I changed my bit of test code as follows:

  test('renders responce into paragraph', async () => {
    render(<ForecastButtons weatherResponce={weatherResponce} city='London' />);
    const button = screen.getByRole('button');
    const label = screen.getByText('Please search for city to see current weather');
    fireEvent.click(button)
    await waitFor(() => {
      expect(label.textContent).toBe(`Current weather in ${weatherResponce.location.name} is ${weatherResponce.current.temp_c} degrees`);
    });
  })

weatherResponce is just a mocked response to a mocked HTTP request, which I am doing with nock.

Related