Enzyme: Component asynchronously setting state from useEffect still triggers "not wrapped in act" warning on mount

Created on 4 Nov 2019  路  4Comments  路  Source: enzymejs/enzyme

Current behavior

Hello,

a component triggering setState from within an async useEffect keeps triggering the 'was not wrapped in act()' warning. I did read through #2073 and #2153 but it's not clear to me whether I am missing something or the issue is still present somehow.

Ccomponent performing a data fetch request on render - in my full setup this would be an Apollo useQuery hook instead but I would like to get a good grip on basics first.

export const HelloUser = () => {
  const [name, setName] = useState("world");
  useEffect(() => {
    fetch("https://randomuser.me/api?inc=name&noinfo")
      .then(body => body.json())
      .then(body => {
        const names = body.results;
        if (names) {
          setName(names[0].name.first);
        }
      });
  }, []);
  return <p>Hello, {name}</p>;
};

Simple test with mocked data. The warning is issued on mount - and also update() is not triggering the state change.

describe("With enzyme", () => {
  it("renders", () => {
    fetch.mockResponseOnce(JSON.stringify({ results: [{ name: { first: "Foo" } }] }));
    // Warning: An update to HelloUser inside a test was not wrapped in act(...).
    const root = mount(<HelloUser />);
    console.log("enzyme before update", root.debug());
    act(() => {
      root.update();
    });
    console.log("enzyme after update", root.debug());
    expect(root.find("p")).toHaveLength(1);
    // not updated with response
    // Expected: "Hello, Foo"
    // Received: "Hello, world"
    expect(
      root
        .find("p")
        .at(0)
        .text()
    ).toEqual("Hello, Foo");
  });
});

A supposedly equivalent test with testing-library works and updates as expected without producing the warning.
js describe("With testing-library", () => { afterEach(() => { cleanup(); }); it("renders", async () => { fetch.mockResponseOnce(JSON.stringify({ results: [{ name: { first: "Foo" } }] })); const wrapper = render(<HelloUser />); console.log("testing-library before response"); wrapper.debug(); await waitForDomChange(wrapper); console.log("testing-library after response"); wrapper.debug(); expect(wrapper.getAllByText("Hello, Foo")).toHaveLength(1); }); });

Expected behavior

No warning :)

Your environment

Windows 10

API

  • [ ] mount

Version

| library | version
| ------------------- | -------
| enzyme | 3.10.0
| react | 16.11.0
| react-dom | 16.11.0
| react-test-renderer |
| adapter (below) | 1.15.1
| node | 10.16.3

Adapter

  • [ ] enzyme-adapter-react-16

Most helpful comment

i've been using this approach, not sure if it's recommended:

const wrapper = mount(<YourComponent />);
await act(async () => wrapper);
wrapper.update();

All 4 comments

act supports async hooks

describe("With enzyme", () => {
  it("renders", async () => {
    fetch.mockResponseOnce(JSON.stringify({ results: [{ name: { first: "Foo" } }] }));
    // Warning: An update to HelloUser inside a test was not wrapped in act(...).
    const root = mount(<HelloUser />);
    await act(async () => {
      await flushPromises();
    });
    console.log("enzyme before update", root.debug());
    root.update();
    console.log("enzyme after update", root.debug());
    expect(root.find("p")).toHaveLength(1);
    // not updated with response
    // Expected: "Hello, Foo"
    // Received: "Hello, world"
    expect(
      root
        .find("p")
        .at(0)
        .text()
    ).toEqual("Hello, Foo");
  });
});

code above should work for you

Thanks a lot, it does work just right.
After reading #2073 and #2153 I thought that this was already wrapped inside mount, but now I understand why it's not the case, and how the await flushPromises here is equivalent to the await waitForDomChange with testing-library.

i've been using this approach, not sure if it's recommended:

const wrapper = mount(<YourComponent />);
await act(async () => wrapper);
wrapper.update();

I wouldn't expect await act(async () => wrapper); to do anything, since that function isn't actually doing anything. You could probably get the same result with await null.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ahuth picture ahuth  路  3Comments

blainekasten picture blainekasten  路  3Comments

AdamYahid picture AdamYahid  路  3Comments

abe903 picture abe903  路  3Comments

timhonders picture timhonders  路  3Comments