facebook/reactMITd083ec1Report / request removal

Testing with act() and Test Renderer

act() defines a React testing scope: work scheduled inside it is placed in an internal queue, and awaited scopes perform additional flushing before they resolve.

react-test-renderer builds an in-memory container and exposes projections of the resulting Fiber and container state, giving tests inspectable output without requiring the inspected representation to be a browser DOM node.

Sources: packages/react/src/ReactAct.js:31-49, packages/react/src/ReactAct.js:135-159, packages/react-test-renderer/src/ReactTestRenderer.js:511-535, packages/react-test-renderer/src/ReactTestRenderer.js:194-248

Core concepts

Act scope

An act scope is a nestable period in which React routes scheduled work into ReactSharedInternals.actQueue instead of platform scheduling APIs.

actScopeDepth tracks nesting, while didWarnNoAwaitAct records that the missing-await warning has already fired.

Sources: packages/react/src/ReactAct.js:31-49, packages/react/src/ReactAct.js:119-130

Renderer queue

The renderer queue is an array of RendererTask callbacks that flushActQueue executes, including continuations and suspended work.

Sources: packages/react/src/ReactAct.js:311-347

Test container

A test container is a plain object whose children array receives rendered Instance and TextInstance values; create passes it to createContainer and then calls updateContainer.

Sources: packages/react-test-renderer/src/ReactTestRenderer.js:468-631

Test instance

A test instance is a wrapper around an allowed Fiber that exposes current props, type, parent, children, and search helpers.

validWrapperTypes identifies function, class, host, forwarding, memo, simple-memo, and sometimes root tags as eligible wrapper types.

Sources: packages/react-test-renderer/src/ReactTestRenderer.js:299-416, packages/react-test-renderer/src/ReactTestRenderer.js:250-259

How act() drains React work

A synchronous act() increments the scope depth, reuses an existing queue or installs a new one, runs the callback, and conditionally flushes legacy-mode updates before restoring legacy batching state.

If the callback throws, the error is stored, the scope is popped, accumulated errors are aggregated, and the error is rethrown.

An asynchronous callback changes the return contract: act() detects a returned thenable and waits for it to resolve before the outermost scope begins its final queue-draining sequence.

Mechanically, awaited act() waits for the callback’s thenable, a queue flush, and any additional work scheduled by microtasks; it resolves only after the recursive drain observes an empty queue, or rejects with accumulated errors.

The awaited path is summarized here.

Awaited act flow — What must finish before awaited act returns?

Evidence

flushActQueue prevents re-entry with isFlushing, invokes each task, follows non-null continuations, and clears the queue only after the whole queue completes. If a task suspends or throws, remaining work stays represented in the queue or error state.

Nested scopes reuse the queue, but leaving scopes out of order triggers an overlapping-act() warning through popActScope.

If an async act() call is not awaited, queueSeveralMicrotasks schedules a delayed check and emits a warning once; the warning recommends await act(async () =>...).

Sources: packages/react/src/ReactAct.js:42-85, packages/react/src/ReactAct.js:87-101, packages/react/src/ReactAct.js:24-29, packages/react/src/ReactAct.js:104-117, packages/react/src/ReactAct.js:135-163, packages/react/src/ReactAct.js:135-159, packages/react/src/ReactAct.js:271-306, packages/react/src/ReactAct.js:311-347, packages/react/src/ReactAct.js:48-49, packages/react/src/ReactAct.js:256-269, packages/react/src/ReactAct.js:119-130, packages/react/src/ReactAct.js:361-366

How the test renderer builds an inspectable tree

create selects a node mock, determines whether the root is concurrent or legacy when that choice is available, optionally enables strict mode, creates the in-memory container, creates the root, and calls updateContainer.

The renderer’s JSON projection reads the container: it returns null for an absent or empty root, converts a single child recursively, and otherwise converts each child while omitting hidden children.

toTree() instead walks Fibers: class and function components become component records, host tags become host records, text becomes text, and structural tags delegate to their children.

The tree walk handles sibling structure with nodeAndSiblingsArray, then removes nested arrays with flatten.

Thus, the inspected output comes from a plain container object or Fiber-derived records rather than from the browser DOM. The code directly establishes the container shape and the Fiber-to-tree conversion; the DOM comparison is an interpretation of that implementation boundary.

Sources: packages/react-test-renderer/src/ReactTestRenderer.js:468-631, packages/react-test-renderer/src/ReactTestRenderer.js:542-575, packages/react-test-renderer/src/ReactTestRenderer.js:104-144, packages/react-test-renderer/src/ReactTestRenderer.js:194-248, packages/react-test-renderer/src/ReactTestRenderer.js:160-168, packages/react-test-renderer/src/ReactTestRenderer.js:171-192

How tests inspect and mutate the result

A ReactTestInstance keeps its Fiber, resolves its current Fiber through _currentFiber, and reads current memoized props. Its children getter delegates to getChildren, which wraps valid nodes and represents host text as strings.

Search methods share one recursive engine: findAll tests the current instance, optionally stops at a shallow match, and otherwise visits non-string children. findAllByType and findAllByProps provide type and props predicates.

Singular helpers use expectOne, failing when the search returns zero or multiple instances.

OperationHandoffPurpose
createcreateContainer, then updateContainerEstablishes and populates the root
updateupdateContainer(newElement, root, null, null)Replaces the rendered element
unmountupdateContainer(null, root, null, null)Removes the tree and clears references

These operations form the renderer’s root lifecycle.

wrapFiber caches one ReactTestInstance per Fiber in fiberToWrapper, checking the alternate Fiber before allocating a new wrapper.

Sources: packages/react-test-renderer/src/ReactTestRenderer.js:299-368, packages/react-test-renderer/src/ReactTestRenderer.js:261-297, packages/react-test-renderer/src/ReactTestRenderer.js:418-441, packages/react-test-renderer/src/ReactTestRenderer.js:399-415, packages/react-test-renderer/src/ReactTestRenderer.js:371-390, packages/react-test-renderer/src/ReactTestRenderer.js:443-457, packages/react-test-renderer/src/ReactTestRenderer.js:468-631, packages/react-test-renderer/src/ReactTestRenderer.js:582-597, packages/react-test-renderer/src/ReactTestRenderer.js:633, packages/react-test-renderer/src/ReactTestRenderer.js:634-644

Why updates outside act() warn

A non-null ReactSharedInternals.actQueue signals that work is inside an act() scope; React routes scheduled tasks into that queue instead of platform APIs.

The reconciler warns when a test update occurs while that queue is null. The warning instructs tests to wrap state-update-causing code in act() before asserting output.

The warning therefore marks a missing test boundary: the update happened when React was not inside the queue-based scope that act() establishes and flushes.

Sources: packages/react/src/ReactAct.js:31-49, packages/react-reconciler/src/ReactFiberWorkLoop.js:5635-5651, packages/react/src/ReactAct.js:31-101

How it connects

The testing boundary belongs to react, while the in-memory renderer belongs to react-test-renderer; the renderer aliases React.act as its local act constant.

The renderer hands root updates to the reconciler through createContainer and updateContainer, then exposes container and Fiber projections through toJSON, toTree, and ReactTestInstance.

For broader context, see React Overview, Fiber Architecture, and Jest Test Infrastructure

Sources: packages/react-test-renderer/src/ReactTestRenderer.js:71, packages/react-test-renderer/src/ReactTestRenderer.js:468-631, packages/react-test-renderer/src/ReactTestRenderer.js:542-580, packages/react-test-renderer/src/ReactTestRenderer.js:299-416, packages/react/src/ReactAct.js:31-49

Key takeaways

  • act() captures work in ReactSharedInternals.actQueue and awaited scopes keep checking asynchronous tasks before resolving.
  • react-test-renderer renders into an in-memory container and exposes JSON, Fiber-derived trees, and test instances.
  • Async act() must be awaited to avoid missing-await and overlapping-scope warnings.
  • Updates outside an active act queue warn because the test has not declared the boundary that should contain and flush them.

Sources: packages/react/src/ReactAct.js:31-49, packages/react/src/ReactAct.js:271-306, packages/react-test-renderer/src/ReactTestRenderer.js:468-631, packages/react-test-renderer/src/ReactTestRenderer.js:194-248, packages/react-test-renderer/src/ReactTestRenderer.js:299-416, packages/react/src/ReactAct.js:119-130, packages/react/src/ReactAct.js:256-269, packages/react-reconciler/src/ReactFiberWorkLoop.js:5635-5651

Want this for your repos?

Try Angada AI Wiki