Dan
Dan

Reputation: 691

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

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!

// @flow
import React, { useEffect, useState } from 'react';
import Sidebar from '../components/Sidebar';
import fetchData from '../apiWrappers/fetchData';

const App = () => {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const getData = async () => {
      try {
        const newData = await fetchData();
        setData(newData);
        setLoading(false);
      }
      catch (e) {
        setLoading(false);
      }
    };
    getData();
    // eslint-disable-next-line
  }, []);
  return (
    <>
      {!loading
        ? <Sidebar />
        : <span>Loading List</span>}
    </>
  );
};
export default App;

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",

Upvotes: 16

Views: 21704

Answers (2)

DarkTrick
DarkTrick

Reputation: 3467

You should not wrap your whole test in act, just the part that will cause state of your component to update.

Something like the below should solve your problem.

 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);

    await act(async () => {
      wrapper.update();
    })
    expect(wrapper.find('span').exists()).toEqual(false);
    expect(wrapper.html()).toMatchSnapshot();
  });

Upvotes: 3

tmhao2005
tmhao2005

Reputation: 17484

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();
});

Upvotes: 20

Related Questions