Why does this official React testing recipe using await/act/async actually work?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
The official React testing pattern with await act(async () => ...) works because it gives React a chance to process all queued updates before the test makes assertions. act is the "flush React work" boundary, and await matters because some of that work is triggered by promises, timers, or async effects rather than finishing immediately.
What act Actually Guarantees
React batches updates and applies them across renders, effects, and microtasks. In a real browser, users naturally wait while those updates finish. In a test, the code often runs much faster than the UI lifecycle, so an assertion can happen before React has finished the work.
act tells React, "everything triggered inside this scope should be processed before I continue." That is why React warns when state updates happen outside an act boundary. The warning is not about syntax style. It is about test timing.
Why the async and await Parts Matter
For synchronous updates, plain act(() => { ... }) is enough. But if the code inside the test causes a promise to resolve later, React cannot flush that future work unless the test waits for it.
That is why the async form exists:
The await keeps the test paused until the promise chain settles and React has had a chance to commit the resulting updates. Without await, the test can move on too early.
A Concrete Example
Consider a component that loads data on mount:
A manual test can wrap the render in async act:
This works because the render triggers an effect, the effect starts a promise, the promise resolves, setState runs, and act keeps flushing until React has settled that chain.
Why It Often Looks Like Magic
It feels surprising because the promise resolution does not appear directly in the assertion code. But act is not guessing. It is observing React's scheduled updates and waiting for them to finish within the async boundary.
That said, act is not a universal "wait for anything" tool. It helps with work that React knows about. If your test depends on external timers, mocked network layers, or custom event loops, those may still need to be advanced or awaited separately.
Testing Library Often Wraps This for You
If you use React Testing Library, many user interactions and async helpers already wrap updates in act. That is why code like await screen.findByText(...) often works without a manual act block.
For example:
This is conceptually doing the same kind of waiting, just through a higher-level testing API.
Common Pitfalls
The most common mistake is forgetting await on async act. Without it, the test continues before the async React work settles.
Another issue is assuming act waits for unrelated async work automatically. It coordinates React updates, not every promise in the universe.
Developers also wrap everything in manual act even when a testing library helper already does it. That adds noise without adding correctness.
Summary
- '
acttells React tests to flush scheduled UI updates before assertions run.' - The async form is needed when those updates are triggered through promises or effects.
- '
await act(async () => ...)works because it keeps the test paused until React settles the work inside that boundary.' - React Testing Library often provides this behavior through higher-level helpers such as
findByText. - Use
actfor React-managed updates, but remember that external async mechanisms may still need separate control.

