After reading this article you should checkout other common mistakes using RTL here:
https://kentcdodds.com/blog/common-mistakes-with-react-testing-library
Ever noticed this warning in your tests?

Noise like this in test runs can be really annoying, and if we read the error in a bid to find out how we might get rid of it, we might think – well simple, I should use act!
But in many cases this isn’t the solution, let’s consider this component.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
const TestComponent = () => { const [value, setValue] = useState<string>() const [valueSynced, setValueSynced] = useState<boolean>() const syncValue = async () => { try { await sendNetworkRequest(value) setValueSynced(true) } catch { setValueSynced(false) } } useEffect(() => { if (value) { void syncValue() } }, [value]) return ( <> <input onBlur={(e) => setValue(e.currentTarget.value)} /> <p>Synced: {valueSynced ? 'Yes' : 'No'}</p> </> ) } |
Fairly basic, to the end user this is an input and some text that indicates whether or not the value is synced. We can see that in our useEffect we call an async function to perform a network request, then after that we set state so a rerender will happen and show the user whether or not their data is synced. It is important to note that we do not await the async function, and this is because the callback passed to useEffect can not be async/ blocking, the rendering and side effects of components must complete synchronously.
So what does this all have to do with using or not using act?? Well let’s say we were to test this component, mocking the network request and then typing something in the input using user-event…
|
1 2 3 4 5 6 7 8 |
const sendNetworkRequestMock = jest.fn() jest.mock('./sendNetworkRequest', () => sendNetworkRequestMock) it('should render', () => { render(<TestComponent />) await userEvent.type(input, 'hello') expect(input).toHaveValue('hello') }) |
It is very possible that we might see this warning. So should we wrap our userEvent.type in act? Well if we look at the source of user-event we would actually find that userEvent.type it is already wrapped in act, so it shouldn’t be needed.
So why are we really getting this warning? The problem is that the test runner finishes before the async function syncValue completes, because as I said before, the rendering and side effects of components are always synchronous, so when the useEffect triggers, the async function gets sent off into the quantum realm, and the side effect completes which causes the test run to consider the test finished. In a nutshell, the test run finishes before setValueSynced state setter is called.
So where can we go from here. The solution is….waiting for something that would be perceivable to the user which proves the operations of the async function have concluded, which hopefully prevents the test run from finishing prematurely, for this we can use waitFor.
There are two things we could wait for, we could wait for the mock to have been called:
|
1 |
await waitFor(() => expect(sendNetworkRequestMock).toHaveBeenCalled()) |
Or we could wait for the UI to show ‘Yes’:
|
1 |
await waitFor(() => expect(screen.getByText('Synced: Yes').toBeInTheDocument()) |
this would be my preferred option because in this case we are testing something that is perceivable in the real world. Checking that a network request mock has been called is more of a digging into the weeds approach, because such operations are abstract from a user’s POV.
We can make one improvement to the above code, instead of using waitFor, we can just use a findBy query:
|
1 |
expect(await screen.findByText('Synced: Yes').toBeInTheDocument()) |
This works because findBy queries use waitFor under the hood, so we achieve the same thing with a more conventional query.
And we will no longer have any act errors in our test run!