Enzyme: Mounted wrapper was not wrapped in act(...) warning still showing

Created on 4 Jun 2019  ·  4Comments  ·  Source: enzymejs/enzyme

Current behavior

I have a component that makes use of the useEffect hook to make an asynchronous call an api (or in the test case a mock of a call to an api) and then call the setState hook with the result.

I've written tests that assert on the outcome of the effect (ie content being returned from the api). I'm using v1.14.0 of the adapter for react-16 my tests pass, snapshots are created and everything appears to be working well, however, a warning is repeatedly logged out from react-dom.

Here's the test in question:

import { mount } from 'enzyme';

import { flushPromises, renderHook, HOOK_WRAPPER_ID } from 'utils/tests';
import useData from './use-data';

const mockURL = 'https://example.com';
const mockConfig = {
  data: ':dataHere',
};

describe('useData', () => {
  it('fetches data config', async () => {
    fetch.mockResponseOnce(JSON.stringify(mockConfig));
    const wrapper = mount(renderHook(() => useData(mockURL)));

    await flushPromises();
    wrapper.update();

    const dataConfig = wrapper.find(`#${HOOK_WRAPPER_ID}`).props().hook;
    expect(dataConfig).toEqual(mockConfig);
  });
});

Here's the warning:

 PASS  src/hooks/use-data.test.js
  ● Console

    console.error node_modules/react-dom/cjs/react-dom.development.js:506
      Warning: An update to TestHookWrapper 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 https://fb.me/react-wrap-tests-with-act
          in TestHookWrapper (created by WrapperComponent)
          in WrapperComponent

https://github.com/airbnb/enzyme/issues/2073 claims to have fixed this, but even after upgrading both enzyme and enzyme-adapter-react-16 I still encounter this warning.

Expected behavior

For no warning to be issued.

API

  • [ ] shallow
  • [X] mount
  • [ ] render

Version

| library | version
| ------------------- | -------
| enzyme | v3.10.0
| react | v16.8.6
| react-dom | v16.8.6
| react-test-renderer | n/a
| adapter (below) | v1.14.0

Adapter

  • [X] enzyme-adapter-react-16
  • [ ] enzyme-adapter-react-16.3
  • [ ] enzyme-adapter-react-16.2
  • [ ] enzyme-adapter-react-16.1
  • [ ] enzyme-adapter-react-15
  • [ ] enzyme-adapter-react-15.4
  • [ ] enzyme-adapter-react-14
  • [ ] enzyme-adapter-react-13
  • [ ] enzyme-adapter-react-helper
  • [ ] others ( )

Most helpful comment

This issue is actually due to async/await support not being present in the act testing util provided by react-dom/test-utils. You can see more about the issue here: https://github.com/facebook/react/issues/14769

The solution that worked for me was upgrading both react and react-dom to v16.9.0-alpha.0. Then I modified my test above to look like:

import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';


import { flushPromises, renderHook, HOOK_WRAPPER_ID } from 'utils/tests';
import useData from './use-data';

const mockURL = 'https://example.com';
const mockConfig = {
  data: ':dataHere',
};

describe('useData', () => {
  it('fetches data config', async () => {
    fetch.mockResponseOnce(JSON.stringify(mockConfig));
    const wrapper = mount(renderHook(() => useData(mockURL)));

    // now act supports async/await syntax
    await act(async () => {
      await flushPromises();
    });

    wrapper.update();

    const dataConfig = wrapper.find(`#${HOOK_WRAPPER_ID}`).props().hook;
    expect(dataConfig).toEqual(mockConfig);
  });
});

@ljharb Thanks for your help and patience troubleshooting this 😃

All 4 comments

enzyme now wraps the things it knows about in act, but if your test is updating it (like in flushPromises) you have to manually wrap that in act.

I am testing a custom hook above that uses useEffect and my custom hook is async... how do you wrap that in act? I can't simply await on act because it doesn't return anything...
I am trying this:

    await act(async () => {
      await flushPromises();
    });

but I get this error:

  console.error node_modules/react-dom/cjs/react-dom-test-utils.development.js:100
    Warning: The callback passed to ReactTestUtils.act(...) function must not return anything.

    It looks like you wrote ReactTestUtils.act(async () => ...), or returned a Promise from the callback passed to it. Putting asynchronous logic inside ReactTestUtils.act(...) is not supported.


  console.error node_modules/react-dom/cjs/react-dom-test-utils.development.js:100
    Warning: Do not await the result of calling ReactTestUtils.act(...), it is not a Promise.

  console.error node_modules/react-dom/cjs/react-dom.development.js:506
    Warning: An update to TestHookWrapper 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 https://fb.me/react-wrap-tests-with-act
        in TestHookWrapper (created by WrapperComponent)
        in WrapperComponent

indeed, act is synchronous.

Can you share the code of renderHook?

This issue is actually due to async/await support not being present in the act testing util provided by react-dom/test-utils. You can see more about the issue here: https://github.com/facebook/react/issues/14769

The solution that worked for me was upgrading both react and react-dom to v16.9.0-alpha.0. Then I modified my test above to look like:

import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';


import { flushPromises, renderHook, HOOK_WRAPPER_ID } from 'utils/tests';
import useData from './use-data';

const mockURL = 'https://example.com';
const mockConfig = {
  data: ':dataHere',
};

describe('useData', () => {
  it('fetches data config', async () => {
    fetch.mockResponseOnce(JSON.stringify(mockConfig));
    const wrapper = mount(renderHook(() => useData(mockURL)));

    // now act supports async/await syntax
    await act(async () => {
      await flushPromises();
    });

    wrapper.update();

    const dataConfig = wrapper.find(`#${HOOK_WRAPPER_ID}`).props().hook;
    expect(dataConfig).toEqual(mockConfig);
  });
});

@ljharb Thanks for your help and patience troubleshooting this 😃

Was this page helpful?
0 / 5 - 0 ratings