Warning: An update to App inside a test was not wrapped in act(...) in enzyme and hooks

Danial picture Danial · Aug 25, 2020 · Viewed 11k times · Source

I have written this component. it fetchs data using hooks and state. Once it is fetched the loading state is changed to false and show the sidebar.

I faced a problem with Jest and Enzyme, as it does throw a warning for Act in my unit test. once I add the act to my jest and enzyme the test is failed!

And, I have added a test like this which works perfectly.

import React from 'react';
import { mount } from 'enzyme';
import fetchData from '../apiWrappers/fetchData';
import data from '../data/data.json';
import App from './App';

jest.mock('../apiWrappers/fetchData');

const getData = Promise.resolve(data);
fetchData.mockReturnValue(getData);

describe('<App/> Rendering using enzyme', () => {
  beforeEach(() => {
    fetchData.mockClear();
  });

  test('After loading', async () => {
    const wrapper = mount(<App />);
    expect(wrapper.find('span').at(0).text()).toEqual('Loading List');

    const d = await fetchData();
    expect(d).toHaveLength(data.length);

    wrapper.update();
    expect(wrapper.find('span').exists()).toEqual(false);
    expect(wrapper.html()).toMatchSnapshot();
  });
});

So, I got a warning:

Warning: An update to App 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 */
});

I did resolve the warning like this using { act } react-dom/test-utils.

import React from 'react';
import { act } from 'react-dom/test-utils';
import { mount } from 'enzyme';
import fetchData from '../apiWrappers/fetchData';
import data from '../data/data.json';
import App from './App';

jest.mock('../apiWrappers/fetchData');

const getData = Promise.resolve(data);
fetchData.mockReturnValue(getData);

describe('<App/> Rendering using enzyme', () => {
  beforeEach(() => {
    fetchData.mockClear();
  });

  test('After loading', async () => {
    await act(async () => {
      const wrapper = mount(<App />);
      expect(wrapper.find('span').at(0).text()).toEqual('Loading List');

      const d = await fetchData();
      expect(d).toHaveLength(data.length);

      wrapper.update();
      expect(wrapper.find('span').exists()).toEqual(false);
      expect(wrapper.html()).toMatchSnapshot();
    });
  });
});

But, then my test is failed.

<App/> Rendering using enzyme › After loading

expect(received).toEqual(expected) // deep equality

Expected: false
Received: true

  35 | 
  36 |       wrapper.update();
> 37 |       expect(wrapper.find('span').exists()).toEqual(false);

Does anybody know why it fails? Thanks!

"react": "16.13.1",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.3",

Answer

tmhao2005 picture tmhao2005 · Aug 27, 2020

This issue is not new at all. You can read the full discussion here: https://github.com/enzymejs/enzyme/issues/2073.

To sum up, currently in order to fix act warning, you have to wait a bit before update your wrapper as following:

const waitForComponentToPaint = async (wrapper) => {
  await act(async () => {
    await new Promise(resolve => setTimeout(resolve));
    wrapper.update();
  });
};

test('After loading', async () => {
  const wrapper = mount(<App />);
  expect(wrapper.find('span').at(0).text()).toEqual('Loading List');
  
  // before the state updated
  await waitForComponentToPaint(wrapper);
  // after the state updated

  expect(wrapper.find('span').exists()).toEqual(false);
  expect(wrapper.html()).toMatchSnapshot();
});