15

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",
skyboyer
  • 22,209
  • 7
  • 57
  • 64
Dan
  • 681
  • 2
  • 11
  • 23

2 Answers2

17

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();
});
tmhao2005
  • 14,776
  • 2
  • 37
  • 44
  • 2
    Thanks for your comment, I had read the link actually. And, I've just tried your code. But, I get the same warning even after using code. The only way to bypass it is to wrap my whole test in an act like this. `test.only('After loading', async () => { await act(async () => { /* the actual test */ });});` But that is too much!! I am using `useEffect` in my ``, that might be an issue. I have seen this comment https://github.com/enzymejs/enzyme/issues/2073, but it is complex to implement it for me. Do you have any other suggestions to do it without wrap every single of my test? – Dan Aug 27 '20 at 12:38
  • Try to increase the timeout `await new Promise(resolve => setTimeout(resolve, 20));` to check if it would help? – tmhao2005 Aug 27 '20 at 12:55
  • Or you can also try to wrap above code in `act()` I guess – tmhao2005 Aug 27 '20 at 12:59
  • I have tried and increased the time even to 100, but still, get the warning. If I wrap the above code in the act, I will get another warning. The only way it works so far is like this: https://stackblitz.com/edit/react-act-warning-with-enzyme?file=src/App.test.js But, I do not want to do like that for every single test, it is kind of too much and a little messy. Do you know anyway I can simplify it ? :) – Dan Aug 27 '20 at 13:05
  • I don't think we necessarily use `wait-for-expect` to make it work :) do you have a minimal code (could be a small repo) which can re-produce the the issue? – tmhao2005 Aug 27 '20 at 13:34
  • No I do not have smaller repo :(, I have tried a lot without `wait-for-expect` but I could not make it. – Dan Aug 30 '20 at 04:10
  • This just saved my day! Thanks – Cels Feb 27 '21 at 22:23
  • this causes the test to hang and evenutally fail with `: Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error:` – kevzettler Jul 23 '21 at 22:52
1

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();
  });
DarkTrick
  • 2,447
  • 1
  • 21
  • 39