Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing asynchronous useEffect

My functional component uses the useEffect hook to fetch data from an API on mount. I want to be able to test that the fetched data is displayed correctly.

While this works fine in the browser, the tests are failing because the hook is asynchronous the component doesn't update in time.

Live code: https://codesandbox.io/s/peaceful-knuth-q24ih?fontsize=14

App.js

import React from "react";

function getData() {
  return new Promise(resolve => {
    setTimeout(() => resolve(4), 400);
  });
}

function App() {
  const [members, setMembers] = React.useState(0);

  React.useEffect(() => {
    async function fetch() {
      const response = await getData();

      setMembers(response);
    }

    fetch();
  }, []);

  return <div>{members} members</div>;
}

export default App;

App.test.js

import App from "./App";
import React from "react";
import { mount } from "enzyme";

describe("app", () => {
  it("should render", () => {
    const wrapper = mount(<App />);

    console.log(wrapper.debug());
  });
});

Besides that, Jest throws a warning saying: Warning: An update to App inside a test was not wrapped in act(...).

I guess this is related? How could this be fixed?

like image 588
Andreas Remdt Avatar asked Jul 12 '19 11:07

Andreas Remdt


2 Answers

Ok, so I think I've figured something out. I'm using the latest dependencies right now (enzyme 3.10.0, enzyme-adapter-react-16 1.15.1), and I've found something kind of amazing. Enzyme's mount() function appears to return a promise. I haven't seen anything about it in the documentation, but waiting for that promise to resolve appears to solve this problem. Using act from react-dom/test-utils is also essential, as it has all the new React magic to make the behavior work.

it('handles async useEffect', async () => {
    const component = mount(<MyComponent />);
    await act(async () => {
        await Promise.resolve(component);
        await new Promise(resolve => setImmediate(resolve));
        component.update();
    });
    console.log(component.debug());
});
like image 182
craigmiller160 Avatar answered Oct 16 '22 04:10

craigmiller160


Following on from @user2223059's answer it also looks like you can do:

// eslint-disable-next-line require-await     
component = await mount(<MyComponent />);
component.update();

Unfortunately you need the eslint-disable-next-line because otherwise it warns about an unnecessary await... yet removing the await results in incorrect behaviour.

like image 30
Wolfshead Avatar answered Oct 16 '22 05:10

Wolfshead