When should I use act() in react-testing-library?

You don’t need to use act() because most of the cases are already wrapped in act().

FlyingSquirrel
3 min readFeb 4, 2022
act

TL;DR;

  • wrapped in act() if I faced some errors related to act(). And if that doesn’t work, I suffered because it didn’t work even I used async-await.
  • userEvent or some event functions are already wrapped in act from testing-library/react, therefore you don’t need to use act() actually. (👉 code)

First things first, I wrapped in act()…

First things first, let’s wrap everything in act(). (giphy, dev.to)

When I joined the current company 5 months ago, there is no tests on the code that I am in charge of, so I set up using testing-library/react, and msw. And I am adding tests one by one.

One of the frequent mistakes I often make is, first things first, I wrapped things in act(). I read Common mistake with React Testing Library a few days ago, I would like to write this article about act().

Do not wrap things in act unnecessarily
Do not wrap things in act unnecessarily (Kent C. Blog)

what they don’t know is that render and fireEvent are already wrapped in act!

What does act() do?

act() takes in a function as its first argument and called it to apply to the DOM(jsdom). Afteract() function is executed, you can make assertions so it helps make tests run closer to what real users would experience.

However, it’s quite verbose when you use act() directly from react-dom/test-utils. It would be better to use act() from testing-library/react to avoid some of the boilerplate.

Please refer to the below to take a look at what act() looks like.

test('Testing', () => {
act(() => {
/* Codes you want to apply to the DOM */
(e.g. ReactDOM.render(<Counter />, container);)
});
/* assume that the code is applied to the DOM */
});

Just focus on the basic logic, except complex things, act returns thenable object after calling a callback function.

However, I don’t know exactly when I can use this object.

You don’t need to use act() actually.

The reason why you don’t need to do is the most of cases like userEvent, render are wrapped in act already.

eventWrapper() in testing-library/dom is called when event functions like userEvent.click are invoked to make changes to jsdom. testing-library/react overrides eventWrapper() so the event functions are executed in act().

import { configure as configureDTL } from '@testing-library/dom';
import act from './act-compat';// overried
configureDTL({
eventWrapper: cb => { // like userEvent.click()
let result;
act(() => {
result = cb();
});
return result;
},
})

Therefore you don’t have to wrap any event like userEvent with act().


act(() => {
userEvent.click(screen.getByText('Component'));
});

userEvent.click(screen.getByText('Component'));

Plus, testing-library/react wrap render with act so you don’t have to go out of your way to use it again 😅

function render(ui, { hydrate = false, ...rest }) {
(...)
act(() => {
if (hydrate) {
ReactDOM.hydrate(wrapUiIfNeeded(ui), container)
} else {
ReactDOM.render(wrapUiIfNeeded(ui), container)
}
})
(...)
}

act calls can be nested, so React team tracks the depth of act() with the variable actScopeDepth based on the stack. But I think it might be able to make unexpected results by wrapping things in act unnecessarily so Kent C. said we don’t need to do.

--

--