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

Created on 27 Mar 2019  Âˇ  44Comments  Âˇ  Source: enzymejs/enzyme

Current behaviour

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.11.2 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:

    it("should render a link block when the api responds with content", () => {
        const linkBlockResponse = {
            title: "Useful links",
            links: [
                {
                    title: "Book a flight",
                    description: "for your next trip",
                    image: {
                        url: "www.someimage.com",
                        name: "some image"
                    },
                    link: {
                        url: "www.google.com",
                        name: "google",
                        target: "_blank",
                    }
                },
                {
                    title: "Gates & Time",
                    description: "Departures & Arrivals",
                    image: {
                        url: "www.someotherimage.com",
                        name: "some other image"
                    },
                    link: {
                        url: "www.google.com",
                        name: "google",
                        target: "_blank",
                    }
                },
            ]
        } as LinkBlock;

        const apiResponse = {
            content: linkBlockResponse
        } as ContentResponse<LinkBlock>;

        const apiMock = jest.fn((id: number) => Promise.resolve(apiResponse));

        const wrapper = enzyme.mount(<LinkBlockComponent id={1} getLinkBlock={apiMock} />);

        return Promise
            .resolve(wrapper)
            .then(() => {
                wrapper.update();
                expect(apiMock).toHaveBeenCalledWith(1);
                expect(wrapper).toMatchSnapshot();
                expect(wrapper.find(LinkBlockHeaderComponent)).toHaveLength(1);
                expect(wrapper.find(LinkBlockLinksComponent)).toHaveLength(1);
            });
    });

Here's the warning:

    console.error node_modules/react-dom/cjs/react-dom.development.js:506
      Warning: An update to LinkBlockComponent 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 LinkBlockComponent (created by WrapperComponent)
          in WrapperComponent

I've attempted wrapping the call to enzyme.mount(<LinkBlockComponent id={1} getLinkBlock={apiMock} />); in an act block, and asserting afterwards as the warning suggests but this ends up calling all my tests before the component has finished rendering.

I don't suspect this is an issue caused enzyme or the adapter itself as I've noticed that you are wrapping mount calls within an act() block further down, however, I've struggled to find any documentation from enzyme on testing the effect of useEffect.

If you have any suggestions on best practices here or if anything looks amiss, help would be appreciated.

Expected behaviour

Your environment

API

  • [ ] shallow
  • [x] mount
  • [ ] render

Version

| library | version
| ------------------- | -------
| enzyme | 3.9.0
| react | 16.8.4
| react-dom | 16.8.4
| react-test-renderer | n/a
| adapter (below) | 1.11.2

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

Using TS-- an even simpler version of @SenP 's solution if you don't need to interact with the page and don't want to install waait is this:

const waitForComponentToPaint = async (wrapper: any) => {
   await act(async () => {
     await new Promise(resolve => setTimeout(resolve, 0));
     wrapper.update();
   });
};

Usage:

it('should do something', () => {
    const wrapper  = mount(<MyComponent ... />);
    waitForComponentToPaint(wrapper);
    expect(wrapper).toBlah...
})

This is obviously not a good general solution-- looking forward to the general fix

All 44 comments

I just stumbled on this issue while looking into this StackOverflow question.

The warning seems to be triggered by asynchronous calls to setData triggered by a useEffect function.

Here is an extremely simple Jest test demonstrating the problem:

import React, { useState, useEffect } from 'react';
import { mount } from 'enzyme';

const SimpleComponent = () => {
  const [data, setData] = useState('initial');

  useEffect(() => {
    setImmediate(() => setData('updated'));
  }, []);

  return (<div>{data}</div>);
};

test('SimpleComponent', done => {
  const wrapper = mount(<SimpleComponent/>);
  setImmediate(done);
});

...which results in this warning:

    console.error node_modules/react-dom/cjs/react-dom.development.js:506
      Warning: An update to SimpleComponent 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 SimpleComponent (created by WrapperComponent)
          in WrapperComponent

I'm having the same problem :(

@brian-lives-outdoors That specific issue can be resolved by wrapping jest.runAllImmediates in act.

Do you have a git repo where I can try this out?

FYI, we just released an alpha (https://github.com/facebook/react/releases/tag/v16.9.0-alpha.0) with an async version of act() that should make this simpler to handle.

I'm getting this for the very first time as well. I'm just testing onBlur, onChange, etc. The only thing different from any other tests is that I'm testing something that uses React.useState (checking for a class to be toggled).

I'm guessing this will be resolved when React hooks are supported in Enzyme.

My tests still work.

@threepointone fwiw, it's actually a problem that act throws if you provide a return value (presumably in your alpha, a non-promise return value) - that's why i can't use it on the shallow renderer.

Separately, the next release of enzyme should address this issue (#2073).

I wouldn’t expect act to work with shallow renderer anyway (or for it to even have the warning, because shallow renderer doesn’t do updates I think?) can any of you share a repo with a failing test that I could have a look at?

@threepointone that's part of a longer conversation, i think - the shallow renderer doesn't do updates, but enzyme's shallow does.

The thing is, act() is renderer specific (there’s one each for test-utils, test-renderer, and the noop-renderers.) there isn’t one for the shallow-renderer because we don’t do updates or effects for it. I’m curious to see how you’d get the warning, hence asking for a failing test repro. Maybe it would still ‘work’ because of its internals, but I can’t be sure without seeing. Lemme know if you’d like me to have a look.

we're using the one from test-utils atm, for mount. I was trying to use the same one for shallow, not realizing it was renderer-specific. It didn't work because it required a DOM, and shallow must work in a node environment.

@threepointone I'm afraid the code is in a private repo belonging to my work so I can't share it. I'm currently on holiday, however, I'll try and replicate the issue in a codepen or something next week.

This is addressed by #2034, so I'm going to close it - it will be in the next release.

when will be the next release? My team is waiting for this fix for one month already...
thx guys

v3.10.0 has now been released.

@ljharb, I'm testing the v.3.10.0, and I continue to have the same problem.
The CHANGELOG not list #2034 in version v3.10.0. Am I missing something?

@vinyfc93 that’s because that PR only affected the react 16 adapter, and has been published for months. Ensure that’s updated, and if you’re still having trouble, please file a new issue.

@ljharb I have ensured both enzyme and enzyme-adapter-react-16 are up to date with the latest versions and I am still seeing this issue.

enzyme: v3.10.0
enzyme-adapter-react-16: v1.14.0

Since this seems to be the same issue as the one above should a new issue be opened?

Yes, please.

The same problem after update
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0"

console.error node_modules/react-dom/cjs/react-dom.development.js:506
Warning: An update to ContextEventsProvider 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 ContextEventsProvider (created by WrapperComponent)
      in WrapperComponent

@albertly you still have to manually wrap some kinds of changes with act - what's your test code look like?

I also have this problem, using:

"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0"

This seems still to be an issue? Could this be opened again or am I missing something?

@richarddd please file a new issue.

I suspect for many the issue is still present due to a limitation with react-dom. I found this post informative, short version is you need to upgrade to v16.9.0-alpha.0. For me, since we use expo, I can't just upgrade react arbitrarily, so I went the route of silencing that particular warning in my test config:

const mockConsoleMethod = (realConsoleMethod) => {
  const ignoredMessages = [
    'test was not wrapped in act(...)',
  ]

  return (message, ...args) => {
    const containsIgnoredMessage = ignoredMessages.some(ignoredMessage => message.includes(ignoredMessage))

    if (!containsIgnoredMessage) {
      realConsoleMethod(message, ...args)
    }
  }
}

console.warn = jest.fn(mockConsoleMethod(console.warn))
console.error = jest.fn(mockConsoleMethod(console.error))

It's not ideal but whatever I want to be able to test my hook components. 😉

I also have this problem, with : "enzyme": "^3.10.0", "enzyme-adapter-react-16": "^1.14.0"

Experiencing the same issue on 3.10, specifically with react-apollo if that helps

@ianks I find the same problem when i use react-apollo in my code

Experiencing it on "enzyme": "^3.9.0" and "enzyme-adapter-react-16": "^1.14.0"

Experencing it too on "enzyme": "^3.10.0" and "enzyme-adapter-react-16": "^1.14.0" with "@apollo/react-testing": "^0.1.0-beta.7"

I'm also still running into this issue. "enzyme": "^3.10.0",, "enzyme-adapter-react-16": "^1.14.0",. - specifically having the issue with react-apollo.

Getting the act(...) warning even if i wrap my test in act.

The same issue with

    "@apollo/react-testing": "^3.1.0",
    "enzyme": "^3.10.0",
    "enzyme-adapter-react-16": "^1.14.0",

@phattruong-agilityio @mhuconcern @heruifeng1 @ianks

If you're still running into this issue with react-apollo specifically, I got this fixed thanks to a helpful reply on my spectrum post.

It seems that when you mount components that include a query, you need to await an act call that pushes the rest of your test to the end of the event loop, even if you don't need to assert on the updated component after the query.

This waait package will help, or you can include the code right in your project, I have these helper functions set up:

// https://github.com/wesbos/waait/blob/master/index.js
export function wait(amount = 0) {
  return new Promise(resolve => setTimeout(resolve, amount));
}

// Use this in your test after mounting if you need just need to let the query finish without updating the wrapper
export async function actWait(amount = 0) {
  await act(async () => {
    await wait(amount);
  });
}

// Use this in your test after mounting if you want the query to finish and update the wrapper
export async function updateWrapper(wrapper, amount = 0) {
  await act(async () => {
    await wait(amount);
    wrapper.update();
  });
}

Then you can use them in your tests like this:

it('does something', async () => {
  const wrapper = mountWithApollo(<Component />, mocks);

  await actWait();
  // Or 
  await updateWrapper(wrapper);

  // assert something here
});

No more act(...) warnings 🎉

@andrewmcgov Thanks for your comment. I've tried. Somehow it doesn't work for me

@andrewmcgov - Your react version needs to be 16.9.0 I believe!

Can you confirm it's the case @andrewmcgov ?

  • When I updated my react version to latest (in my case ~16.10.2 )
  • Then tried the example found above ⬆️

Then the warnings went away

@andrewmcgov Please note the way you wrote waiit it's more like waait
When I tried npm i waiit it obviously didn't work 😆

I ended up with a slight variation of @andrewmcgov 's solution:

const actions = async (wrapper, _actions) => {
  await act(async () => {
    await (new Promise(resolve => setTimeout(resolve, 0)));
    _actions();
    wrapper.update();
  });
}

and in the tests:

const wrapper = ...
await actions(wrapper, () => {
  // perform required updates
});

@SenP Do you have any idea how to make it work with TS?

Is there any other solution apart from installing waait package?

I am seeing the warning, even if I have wrapped my component with act... Any solution incoming?

Seeing this issue as well, commenting to follow thread, also using TS

Using TS-- an even simpler version of @SenP 's solution if you don't need to interact with the page and don't want to install waait is this:

const waitForComponentToPaint = async (wrapper: any) => {
   await act(async () => {
     await new Promise(resolve => setTimeout(resolve, 0));
     wrapper.update();
   });
};

Usage:

it('should do something', () => {
    const wrapper  = mount(<MyComponent ... />);
    waitForComponentToPaint(wrapper);
    expect(wrapper).toBlah...
})

This is obviously not a good general solution-- looking forward to the general fix

Thanks @edpark11 – your solution is the cleanest for me until something official upstream.

Added some slight type safety:

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


export async function waitForComponentToPaint<P = {}>(
  wrapper: ReactWrapper<P>,
  amount = 0,
) {
  await act(async () => {
    await new Promise(resolve => setTimeout(resolve, amount));
    wrapper.update();
  });
}

In my case, it seems useEffect needs amount of time to be run. So, I fixed with jest.runAllImmediates.

it('render a LocationSelector', () => {
  jest.useFakeTimers()

  const spy = jest.spyOn(console, 'error')
  spy.mockImplementation(() => {})

  const store = mockStore({
    common: { isInternetReachable: true }
  })

  const getLocation = () => jest.fn()
  const close = () => jest.fn()
  const isVisible = true

  const wrapper = mount(
    <Provider store={store}>
      <LocationSelector
        isVisible={isVisible}
        getLocation={getLocation}
        close={close}
      />
    </Provider>
  )

  act(() => {
    jest.runAllImmediates()
  })

  spy.mockRestore()
})

Using TS-- an even simpler version of @SenP 's solution if you don't need to interact with the page and don't want to install waait is this:

const waitForComponentToPaint = async (wrapper: any) => {
   await act(async () => {
     await new Promise(resolve => setTimeout(resolve, 0));
     wrapper.update();
   });
};

Usage:

it('should do something', () => {
    const wrapper  = mount(<MyComponent ... />);
    waitForComponentToPaint(wrapper);
    expect(wrapper).toBlah...
})

This is obviously not a good general solution-- looking forward to the general fix

_use your methods,I still get the error_

console.error
      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.

timeouts and retires are the worse way to test anything. I can't stand tests with timeouts in them. Cypress is notorious for this.

I found that this is really the only way you can test Hooks or use spies, both of which are horrendous ways to write tests. Flaky tests, slow tests, neither is good and is a result of writing any kind of test this way. It's unfortunate.

The React Testing Lib has waitFor which is just sugar to say "Lets retry based in timeouts" which is a terrible way to test components. The React Testing Library overall is really "Let me provide you some more sugar over the bad, to make you feel like it's better testing but it really isn't".

Without a component lifecycle like Class components had that allowed us to observe and find out truly when an internal async call was resolved the _first_ time (which I believe is what enzyme was able to do with class components), we're stuck with really bad routes to test Hooks no matter what testing lib we're using.

Also, If I'm not wrong, you have to test on _second_ render of a Hook, because useEffect which if you call setState, updates state to cause a re-render, is not called till after you mount or render it the first time.

So the lack of a lifecycle to observe and the fact that we're stuck with how React forces Hooks to run (first render, then re-render with useEffect), we're stuck with either these two routes to test:

  • using a spy via mocking framework or custom spy which is bad because you're spying on internals from your test
  • using timeouts and retries, which makes you write flaky, unnecessarily complicated tests, and slower tests

This is my solution without setTimout
I hope this helps someone.

Helper:

import {waitFor} from "@testing-library/react"
import {ReactElement} from "react";
import {ReactWrapper} from "enzyme";

interface asyncRenderModel {
    (renderMethod: (node: ReactElement) =>
            ReactWrapper<any>, component: ReactElement): Promise<ReactWrapper<any>>
}

const asyncRender: asyncRenderModel = async (renderMethod, component) => {
    let result = renderMethod(<></>);
    try {
        await waitFor(() => {
            result = renderMethod(component)
        })
    } catch (e) {
        console.error(e)
    }
    return result;
}
export default asyncRender

How to use:

test('Should render component', async () => {
        const app = await asyncRender(mount, <Component/>);

        expect(app.find(SomeElement)).toHaveLength(1)
 })
Was this page helpful?
0 / 5 - 0 ratings

Related issues

potapovDim picture potapovDim  Âˇ  3Comments

blainekasten picture blainekasten  Âˇ  3Comments

abe903 picture abe903  Âˇ  3Comments

benadamstyles picture benadamstyles  Âˇ  3Comments

mattkauffman23 picture mattkauffman23  Âˇ  3Comments