When to Use act in React Testing Library with Jest: A Complete Guide
Wrap any code that triggers React state updates, effects, or re-renders with act() to ensure the DOM is stable before assertions, preventing "state update not wrapped in act" warnings and flaky tests.
The act utility is essential for writing reliable unit tests in React DOM applications using Jest. When testing components in the jestjs/jest ecosystem alongside React Testing Library, understanding when to explicitly wrap code with act in React Testing Library prevents asynchronous state warnings and ensures your assertions run against a fully updated DOM.
What Is act in React Testing Library?
The act function is a testing utility that guarantees all React updates—including state changes, prop updates, and effect executions—are flushed before you make any assertions. According to the Jest documentation in website/versioned_docs/version-30.0/TutorialReact.md, act ensures that the component is in a stable state when you read the DOM or capture snapshots.
When you wrap code with act, React processes all pending work synchronously, preventing the common warning: "An update to Component inside a test was not wrapped in act."
When Should You Wrap Code with act?
Understanding when to explicitly use act in React Testing Library depends on what triggers the React update. While React Testing Library automatically wraps many utilities like fireEvent and render with act, certain scenarios require manual intervention.
Synchronous State Updates
For standard user interactions using React Testing Library's event utilities, you typically do not need additional act wrappers. The fireEvent method in examples/react-testing-library/__tests__/CheckboxWithLabel-test.js is already wrapped internally. However, if you call a component method directly that triggers setState, you must wrap it with act.
Asynchronous Operations and Promises
Any code that involves await, Promise resolution, or async state updates requires await act(async () => { ... }). This pattern ensures that all effects and state updates triggered by the promise resolution are flushed before assertions run.
Timer-Based Updates
When testing components that use setTimeout, setInterval, or requestAnimationFrame, wrap timer advancements with act. This is critical when using Jest's fake timers to ensure React processes the timer callbacks correctly.
Direct Component Method Calls
If your test calls methods directly on a component instance or ref that update state—rather than simulating DOM events—you must wrap these calls with act. This pattern appears when testing legacy components or imperative handles, though React Testing Library favors user-centric testing that typically avoids this approach.
Practical Code Examples
The following examples demonstrate proper usage of act in React Testing Library based on patterns found in the jestjs/jest repository.
Example 1: Standard Event Handling (No Extra act Needed)
React Testing Library automatically wraps fireEvent with act. This example from examples/react-testing-library/__tests__/CheckboxWithLabel-test.js shows a typical pattern:
import {render, fireEvent} from '@testing-library/react';
import CheckboxWithLabel from '../CheckboxWithLabel';
test('checkbox toggles when clicked', () => {
const {getByLabelText} = render(<CheckboxWithLabel label="Accept" />);
const checkbox = getByLabelText(/accept/i);
expect(checkbox).not.toBeChecked();
fireEvent.click(checkbox); // RTL internally wraps this in act
expect(checkbox).toBeChecked();
});
Example 2: Direct Component Method Calls
When calling component methods directly that trigger state updates, explicitly wrap with act:
import {act, render} from '@testing-library/react';
import Counter from '../Counter';
test('counter increments via method call', () => {
const {container, getByText} = render(<Counter />);
// Counter exposes an increment method (not a DOM event)
act(() => {
container.firstChild.increment(); // triggers setState
});
expect(getByText(/count: 1/i)).toBeInTheDocument();
});
Example 3: Asynchronous State Updates
For async operations, use await act(async () => { ... }) to ensure promises resolve before assertions:
import {act, render, screen, waitFor} from '@testing-library/react';
import AsyncButton from '../AsyncButton';
test('button shows loading then result', async () => {
render(<AsyncButton />);
fireEvent.click(screen.getByRole('button', {name: /load/i}));
// Wait for the async state change
await act(async () => {
await waitFor(() => expect(screen.getByText(/done/i)).toBeInTheDocument());
});
});
Example 4: Timer-Based Updates with Fake Timers
When testing components with timers, wrap timer advancements with act:
import {act, render, screen} from '@testing-library/react';
import TimerComponent from '../TimerComponent';
jest.useFakeTimers();
test('timer updates after 1 second', () => {
render(<TimerComponent />);
act(() => {
jest.advanceTimersByTime(1000); // triggers setTimeout inside component
});
expect(screen.getByText(/elapsed: 1s/i)).toBeInTheDocument();
});
Implementation Details in the Jest Repository
Understanding how act in React Testing Library integrates with Jest requires examining specific files in the jestjs/jest repository.
The official Jest tutorial documents renderer.act usage for snapshot testing in website/versioned_docs/version-30.0/TutorialReact.md (lines 106-114). This demonstrates the canonical pattern for wrapping React updates in act when using the React test renderer directly.
For React Testing Library specifically, the reference implementation appears in examples/react-testing-library/__tests__/CheckboxWithLabel-test.js. This file illustrates how RTL automatically handles act wrapping for fireEvent calls, serving as the practical baseline for most DOM testing scenarios.
The core test execution environment that surfaces act warnings is managed by packages/jest-runner/src/runTest.ts. This runner executes test files and captures console warnings, including React's "not wrapped in act" notifications.
While React Testing Library provides its own act export (re-exported from React DOM), the underlying implementation in the React test renderer resides in packages/react-test-renderer/src/renderer.ts. This file provides the act function that ensures all React work is flushed before continuing.
For broader context on testing framework integration, website/versioned_docs/version-30.0/TestingFrameworks.md lists recommended testing frameworks (including React Testing Library) and links to official documentation discussing act usage patterns.
Summary
- Wrap synchronous state updates with
actwhen calling component methods directly, though React Testing Library automatically wrapsfireEventandrendercalls. - Use
await act(async () => { ... })for any asynchronous operations, promise resolutions, or async state changes to ensure effects complete before assertions. - Always wrap timer advancements with
actwhen usingjest.advanceTimersByTimeor similar timer mocks to trigger React updates correctly. - Reference canonical examples in
website/versioned_docs/version-30.0/TutorialReact.mdandexamples/react-testing-library/__tests__/CheckboxWithLabel-test.jsfor implementation patterns. - Prevent warnings by ensuring all React work is flushed, eliminating "state update not wrapped in act" console noise in
packages/jest-runner/src/runTest.tsexecution environments.
Frequently Asked Questions
When do I need to manually wrap code with act in React Testing Library?
You need to manually wrap code with act when performing actions that React Testing Library does not automatically handle. While render and fireEvent are already wrapped internally, you must use act when calling component methods directly via refs or instances, advancing Jest fake timers, or resolving asynchronous promises that trigger state updates. According to the Jest repository's examples/react-testing-library/__tests__/CheckboxWithLabel-test.js, standard DOM events require no manual wrapping, but direct state mutations do.
Why does React warn about updates not being wrapped in act?
React issues this warning when state updates, effect executions, or re-renders occur outside of an act context during test execution. The warning surfaces through the test runner defined in packages/jest-runner/src/runTest.ts to alert developers that assertions might run against stale DOM states. Wrapping updates with act ensures React flushes all pending work synchronously, making tests deterministic and eliminating console noise.
Should I use act with React Testing Library's render and fireEvent?
No, you generally should not wrap render or fireEvent calls with additional act blocks because React Testing Library already wraps these utilities internally. The source code in website/versioned_docs/version-30.0/TutorialReact.md demonstrates that renderer.act is used internally by the testing framework. However, if you are using the React test renderer directly or calling methods on component instances, you must explicitly use act from react-test-renderer or @testing-library/react.
How do I test asynchronous updates with act in Jest?
For asynchronous operations, use the async variant: await act(async () => { ... }). This pattern ensures that promises resolve and all resulting state updates and effects complete before the test continues. As shown in the Jest repository examples, you should wrap waitFor calls or direct promise resolutions inside the async act block when testing components that fetch data or use setTimeout. This prevents race conditions and ensures the DOM reflects the final state when assertions run.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →