react-testing-library version: 9.1.4react version: 16.9.0react-dom version: 16.9.0react-scripts version: 3.0.1axios version: 0.19.0jest-dom version: 4.1.0node version: Code Sandbox defaultnpm (or yarn) version: Code Sandbox defaultimport React from "react";
import useFetch from "./useFetch";
export default function App() {
const { data, error, loading } = useFetch(
"https://jsonplaceholder.typicode.com/posts"
);
if (loading) {
return <div data-testid="loading">Loading...</div>;
}
if (error) {
return <div>Error: {error.message}</div>;
}
return (
<div>
Hello World
{data && data.map(item => <div>{item.title}</div>)}
</div>
);
}
import React from "react";
import { render } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";
import App from "./App";
test("loading state is in the document before network request completes", () => {
const { getByTestId } = render(<App />);
const loading = getByTestId("loading");
expect(loading).toBeInTheDocument();
});
import { useEffect, useState } from "react";
import axios from "axios";
const useFetch = url => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
(async () => {
setLoading(true);
try {
const res = await axios(url);
setData(res.data);
setLoading(false);
} catch (e) {
setError(e);
setLoading(false);
}
})();
}, [url]);
return { data, error, loading };
};
export default useFetch;
I am writing a test to test the loading the state of my app. The test passes but I still get the act warning. Here is a Code Sandbox to mess with it. (See the console to view the warning)
I have the latest version of React, but I can not figure out why this is triggering the act warning.
Warning: An update to App 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 App (at App.test.js:7)
Here is a Code Sandbox to mess with it.
Your effect is updating the component state after the test completes. To get rid of the warning, you can either wait for the result of the fetch, or ensure that it never resolves (e.g. by mocking).
Here's an example of waiting for the effect that Kent shared on a different issue that helped me fix this in my own tests.
Thanks @grncdr for the guidance. I tried that and unfortunately, I am still getting the act warning. My Code Sandbox is updated with this change.
import React from "react";
import { render, wait } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";
import App from "./App";
test("loading state is in the document before network request completes", async () => {
const { getByTestId } = render(<App />);
await wait(() => expect(getByTestId("loading")).toBeInTheDocument());
});
My useFetch immediately updates state when the component mounts (loading -> true). So, could the render kicking off a state change be the problem? The reason I think that is cause if you comment out the expect and only have the render in the test, the act warning still occurs.
The problem is the same. After your test finishes, the promise resolves with the data and triggers an update (and the warning). This is what you want: https://codesandbox.io/s/magical-cookies-w2jbf
import React from 'react'
import {render, wait} from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'
import App from './App'
test('loading state is in the document before network request completes', async () => {
const {getByTestId, getByText} = render(<App />)
expect(getByTestId('loading')).toBeInTheDocument()
expect(await findByText(/hello/i)).toBeInTheDocument())
})
To take it further, you probably want to mock axios so your test isn't actually making the request that it is.
That makes complete sense and it is working now! I left out the axios mock for this test because I did not think the loading render needed to be aware of that. However, I see that your approach is to mock the response and test the loading _before_ the resolution/rejection. Thanks Kent!
You may appreciate this post I wrote a few weeks ago: https://kentcdodds.com/blog/write-fewer-longer-tests
I updated the test in my comment above to be better :)
Hm, this is what worked for me:
import React from 'react';
import { render, waitForElement } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import axios from 'axios';
import App from './App';
jest.mock('axios');
test('shows loading while network request is in progress', async () => {
axios.mockResolvedValue({ data: {} });
const { getByTestId, queryByTestId } = render(<App />);
const loading = queryByTestId('loading');
expect(loading).toBeInTheDocument();
await waitForElement(() => getByTestId(/hello/i));
});
Based on your course, I was under the impression that expect(getByTestId('loading')).toBeInTheDocument() is sort of redundant since the test will fail if the data-testid is not found anyway.
Yes, it is redundant, but I often include it anyway because it makes the code clearer.
To take it further, you probably want to mock axios so your test isn't actually making the request that it is.
Regarding this comment 馃憜, I've mocked the fetch to resolve immediately but I'm still getting the warning, i'm thinking that since the fetch is a dependency of useEffect maybe the instance has changed and that's why it's not mocked anymore?
// App
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
fetch().then(() => {
setLoading(false); // this is causing the act warning although fetch is mocked
});
}, [fetch])
// spec
const mockFetch = jest.fn(() => Promise.resolve());
test('calls fetch', () => {
render(<App fetch={mockFetch} />);
expect(mockFetch).toBeCalled();
});
any thoughts? sry I know this could probably live under SO
EDIT [SOLUTION]
Ok, I posted to soon, the solution as stated here is to wrap the assertion in async/await
test('calls fetch', async() => {
render(<App fetch={mockFetch} />);
await wait(() => expect(mockFetch).toBeCalled());
});
I also would like to bring attention to @kentcdodds lovely written blog article about the same subject and included Egghead videos: https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning
Personally, I have a similar issue when I am using Apollo Hooks with Downshift but haven't discovered yet what's causing the act() warning. I wish sometimes React could give a bit more hints :)
If you're just looking to suppress these warnings, here's what worked for me in expo react native project using jest:
package.json has no "jest" configjest.config.js in the root directory looks like this:module.exports = {
preset: "jest-expo", // only needed for exp
// this is the important bit, it looks like these setup files
// have access to `beforeAll`, `beforeEach` etc
setupFilesAfterEnv: ["./jest-setup.js"]
};
jest-setup.js in the root directory looks like this:beforeAll(() =>{
const originalConsoleError = console.error;
console.error = (...args) => {
if (
!args[0].startsWith(
'Warning: An update to %s inside a test was not wrapped in act(...).',
)
) {
originalConsoleError(...args);
}
};
})
I stole the console.error = bit from some issue or another about this.
I strongly advise against this. The warning is telling you something important and should not be ignored. As @weyert shared: https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning
@kentcdodds Appreciate the advice!
From what I can tell in my case, the async update happening inside the component that is causing the warning to be thrown is caused by Apollo's useQuery hook running inside the component, and calling Apollo's <MockedProvider>.
After spending hours on it, I don't think I'm smart enough to work out how to write either the test or the component such that I don't get these warnings, and given that there's dozens of these threads, I'm guess I'm not the only one.
The documentation only clearly states for findBy* that a promise is returned:
findBy*queries return a promise which resolves when an element is found which matches the given query. The promise is rejected if no element is found or if more than one element is found after a default timeout of4500ms.
In contrast the docs for getBy* do not state that a promise is returned.
getBy*queries return the first matching node for a query, and throw an error if no elements match or if more than one match is found (usegetAllByinstead).
The documentation only clearly states for
findBy*that a promise is returned:
findBy*queries return a promise which resolves when an element is found which matches the given query. The promise is rejected if no element is found or if more than one element is found after a default timeout of4500ms.In contrast the docs for
getBy*do not state that a promise is returned.
getBy*queries return the first matching node for a query, and throw an error if no elements match or if more than one match is found (usegetAllByinstead).
Thank you! I wasn't able to make it work, changed getByText for findByText and _voila!_
I wanted to add my two cents here because I spent 2 hours banging my head against the wall with the dreaded act warning.
I was making an async call on page load:
const Page = () => {
const [someState, setSomeState] = useState();
useEffect(() => {
(async () => {
const { data, errors } = await mswMockedAPICall();
setSomeState(data);
})();
}, []);
return (
<div>
<h1>{someState ?? "Loading..."}</h1>
<p>Some content that is loaded in the initial page load</p>
</div>
);
};
The way I circumvented / got rid of the warning was by waiting for the async content to be populated before each test.
beforeEach(async () => {
renderWithWrappers(<Signup />, routerArgs);
expect(
await screen.findByText(/the async content here/i)
).toBeInTheDocument();
});
and then test for the other things that expect on the screen:
expect(screen.getByText(/initial page load/i)).toBeInTheDocument();
I appreciate that this wouldn't really work if you had multiple async calls. But it solved my issue of state updates / rerenders inside the tests. Would be interested if anyone has come up with better workarounds.
I've seen several issues about this particular topic, and while the recommendations are in general to use await and findBy*, none of these solutions answer the problem raised in many cases, which is when the warning comes from an update being done in a custom hook within one of the wrapper components.
As a specific example, think of a TranslationProvider that returns null until the translations are ready. In turn, the translations are being fetched asynchronously, which means that an act console error will be thrown when the ready state changes. See example code below:
const useFetchTranslations = () => {
const [ready, setReady] = useState(false);
useEffect(() => {
(async () => {
try {
await fetchTranslations();
setReady(true); // console error points here
} catch (e) {
// do some error handling
}
})();
}, [setReady]);
return ready;
};
const TranslationProvider = ({ children }) => {
const ready = useFetchTranslations();
if (!ready) return null;
return children;
};
How do you account for this scenario without using a hack like https://github.com/testing-library/react-testing-library/issues/480#issuecomment-598212226, which is frankly the only option I see at this point?
Most helpful comment
Your effect is updating the component state after the test completes. To get rid of the warning, you can either wait for the result of the fetch, or ensure that it never resolves (e.g. by mocking).
Here's an example of waiting for the effect that Kent shared on a different issue that helped me fix this in my own tests.