React: Why Not To Use act() (In Most Of The Cases)

2023-01-08
React
testing
best practices
Jest

The Problem

As someone new to the React and its testing, I was reviewing some tests we have and (perhaps obviously) noticed a lot of usage of act() function.

And so I went along with it until I found a construct that looked suspiciously excessive:

await act(async () => { render(); });

I simplify it here, but the gist is wrapping component in async function inside act().

So I started to digging for why to we need act at all, and turned out that in most cases we actually don't need it. There's a very good article about it, "You Probably Don’t Need act() in Your React Tests" - it explains what act() does, and turns out most of the functions provided by React testing library are already doing what act() does - so things like render, userEven, fireEvent already wrap the code in act().

The code mentioned above tries to cope with An update to App inside a test was not wrapped in act(...) error, which happens due to asynchronous code running in component and being executed in event loop after the test code. So wrapping it in async looks like it solves the problem, but it really doesn't.

From the aforementioned article, the solution above is equivalent to

render(); await act(async () => {});

and only works because new await() happens in the next tick of the event loop.

The solution

The right way to address this would be to use either waitFor:

await waitFor(() => expect(screen.getByText("something")).toBeInTheDocument());

or the async variants of search in rendered document, e.g.

expect(await screen.findByText("something")).toBeInTheDocument();

When to use act()

There are certainly still cases where act() might be needed, here's a large collection that I'm yet to go through - though at least some cases (related to hooks) could be handled by renderHook and the waitForNextUpdate that it returns.

UPD: A short overview of the use cases for the act(): https://hydralien.net/blog/posts/react-when-to-use-act/