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().
TL;DR;
- wrapped in
act()
if I faced some errors related toact()
. 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 fromtesting-library/react
, therefore you don’t need to useact()
actually. (👉 code)
First things first, I wrapped in act()…
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()
.
what they don’t know is that
render
andfireEvent
are already wrapped inact
!
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.
- React (act is born here)
- testing-library/dom (provides configure function for testing DOM)
- react-testing-library (overrides with new function wrapped in act() using configure function from testing-library/dom)
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';// overriedconfigureDTL({
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.