Fix Warning in React: Update was not wrapped in act()
il3ven
Posted on March 6, 2021
The purpose of this article is to fix the following error.
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://reactjs.org/link/wrap-tests-with-act
Why am I seeing this?
In my experience this happens because your component performs some state change after the test has finished running.
How to fix this?
Add more expect statements to expect the change in state. This makes sure that tests do not finish before.
Example
Let's look at a code that shows the act()
warning and then try to fix that code.
Bad Code
App.js
function App() {
const [todo, setTodo] = useState("");
useEffect(() => {
const fetch = async () => {
const _todo = (
await axios.get(`https://jsonplaceholder.typicode.com/todos/1`)
).data;
setTodo(_todo.title);
};
fetch();
}, []);
return (
<div className="App">
<h1>React App</h1>
<h2 data-testid="title">Title: {todo}</h2>
</div>
);
}
View full file
Here we fetch some data from API in useEffect
then setState
to show the data in a h2
tag.
App.test.js
jest.mock("axios");
test("renders learn react link", () => {
axios.get.mockImplementation(() => {
return {
data: {
userId: 1,
id: 1,
title: "delectus aut autem",
completed: false,
},
};
});
render(<App />);
expect(screen.getByText(/React App/i)).toBeInTheDocument();
});
View full file
Why the above test does not work?
React Testing Library renders <App/>
component. After the initial render it expects React App to be present. Since, it is present the test will finish.
useEffect
is called after the initial render and calls setTodo
to set state. But the tests have finished until then.
Maybe after setting the state something changes and your tests start failing but that would not be tested. So, you should not be ignoring these warnings.
Good code
To fix the above error you should make sure tests finish after all the state changes have been done.
To do this we add the following line in the above App.test.js
.
test("renders learn react link", () => {
axios.get.mockImplementation(() => {
return {
data: {
userId: 1,
id: 1,
title: "delectus aut autem",
completed: false,
},
};
});
render(<App />);
expect(screen.getByText(/React App/i)).toBeInTheDocument();
+ expect(screen.getByTestId('title').innerHTML).toBe('Title: delectus aut autem');
});
View full file
The above alone will not work. This is because the expect runs before the state has been set. To solve this problem we can use waitFor
from @testing-library/react
. This will wait for whatever is inside it to be true or timeout.
- expect(screen.getByTestId('title').innerHTML).toBe('Title: delectus aut autem');
+ await waitFor(() => {
+ expect(screen.getByTestId("title").innerHTML).toBe(
+ "Title: delectus aut autem"
+ );
+ });
View full file
Now the tests work fine without any warnings. You can view the full working code at this repository.
Posted on March 6, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 30, 2024
November 30, 2024