Why do I need to wrap React/Enzyme state changes in act?

jared_hexagon picture jared_hexagon · Sep 27, 2019 · Viewed 9.5k times · Source

In my simple React/ReactDOM/Enzyme unit test I get a warning from ReactDOM about wrapping any mutations to state in act(). Why do I need to do this if my test passes anyway? I have 50 or so React components that all use hooks, custom hooks, etc. and I never wrap in act() and they all pass.

Can I just disable this warning? I don't want to add extra code for what appears to be no reason.

The warning:

Warning: An update to MyComponent 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 */
    });
    /* assert on the output */

    This ensures that you're testing the behavior the user would see in the browser. Learn more at [redacted]

My test:

const App = () => {
  const [isLoaded, setIsLoaded] = useState(false);

  const myOnClick = () => {
    setIsLoaded(true);
  };

  return (
    <div onClick={myOnClick}>
      {isLoaded ? 'Yes' : 'No'}
    </div>
  )
}
describe('My test', () => {
  let wrapper

  beforeAll(() => {
    wrapper = mount(<App />)
  })

  it('renders "no"', () => {
    expect(wrapper.text()).toBe('No')
  })

  describe('When onClick is called', () => {
    beforeAll(() => {
      wrapper.find('div').prop('onClick')()
    })

    it('renders "yes"', () => {
      expect(wrapper.text()).toBe('Yes')
    })
  })
})

CodeSandbox repro: https://codesandbox.io/s/react-169-act-warning-repro-olm8o?expanddevtools=1&fontsize=14&hidenavigation=1&previewwindow=tests

Answer

skyboyer picture skyboyer · Sep 27, 2019

act() in first place runs all related useEffect that would be async way otherwise. You don't see any difference since you don't have useEffect in your component's code

According to Enzyme's docs in most recent version mount() should be already wrapped with act() internally:

If you're using React 16.8+ and .mount(), Enzyme will wrap apis including .simulate(), .setProps(), .setContext(), .invoke() with ReactTestUtils.act() so you don't need to manually wrap it.

We cannot wrap the result of .prop() (or .props()) with .act() in Enzyme internally since it will break the equality of the returned value. However, you could use .invoke() to simplify the code:

const wrapper = mount(<SomeComponent />);
wrapper.invoke('handler')();
expect(/* ... */);```