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);
});
});
No warning :)
Windows 10
| 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
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.
Most helpful comment
i've been using this approach, not sure if it's recommended: