Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 502
jared_hexagon Avatar asked Sep 27 '19 02:09

jared_hexagon


2 Answers

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(/* ... */);```
like image 85
skyboyer Avatar answered Oct 31 '22 03:10

skyboyer


The line causing the act() warning is

wrapper.find('div').prop('onClick')()

As @skyboyer's answer advised prop is not wrapped with act internally. When you call the onClick and update state, react is warning that something changed unexpectedly. If you use .simulate instead you do not need to wrap it with act.

wrapper.find('div').simulate('click')
like image 34
Brian Avatar answered Oct 31 '22 04:10

Brian