Jest Test Infrastructure
React’s Jest infrastructure makes the same tests run against source modules, release-channel variants, and compiled bundles. It combines Jest configuration, setup files, feature-flag inspection, host-config mocking, and internal scheduling assertions.
This exists because React’s behavior depends on both build mode and renderer. The test helpers therefore make asynchronous work, console output, expected failures, and renderer-specific module forks observable and deterministic.
Sources: scripts/jest/config.build.js:57-75, packages/internal-test-utils/ReactInternalTestUtils.js:43-90, packages/internal-test-utils/internalAct.js:42-195, scripts/jest/setupHostConfigs.js:188-213
Core concepts
Release-channel flags
A release-channel flag set is a computed view of build globals, environment state, and feature-flag modules. getTestFlags derives modern, classic, source, www, and fb, while environmentFlags supplies experimental, stable, and other environment values; the result exposes feature flags through a proxy that rejects unknown names.
Sources: scripts/jest/TestFlags.js:56-126, scripts/jest/TestFlags.js:35-54, scripts/jest/TestFlags.js:114-123
Gated test expectations
A gated test condition is either a flag name or a predicate over the flag set. coerceGateConditionToFunction converts a string such as foo into a function that reads flags[foo], while an existing function is used unchanged. When a test is expected to fail, expectTestToFail runs its callback, captures thrown errors and console failures, and throws a Jest failure if nothing went wrong.
Sources: scripts/jest/setupTests.js:235-241, scripts/jest/setupTests.js:181-233
Scheduler assertions
A scheduler assertion compares the expected sequence of logged work with the Scheduler mock’s cleared log. assertLog clears the log, returns when it matches, and otherwise throws a diff.
Sources: packages/internal-test-utils/ReactInternalTestUtils.js:312-325
Internal act scope
The internal act scope is a test-only flush boundary that controls Scheduler work, timers, console assertions, and globally reported errors. act requires the special Scheduler mock and Jest timer mocks, starts after a microtask, and deliberately sets global.IS_REACT_ACT_ENVIRONMENT to false for its outermost scope.
Sources: packages/internal-test-utils/internalAct.js:42-195
Host-config fork
A host-config fork is the renderer-specific implementation selected for a reconciler configuration module. mockAllConfigs derives candidate fork filenames from rendererInfo.shortName, tries progressively shorter names, and loads the first existing candidate.
Sources: scripts/jest/setupHostConfigs.js:188-213
How Jest selects the test world
The source configurations extend a shared base configuration and add environment-specific setup files. The www configuration adds setupTests.www.js and setupHostConfigs.js; the persistent configuration adds setupTests.persistent.js and setupHostConfigs.js; and the xplat configuration adds setupTests.xplat.js and setupHostConfigs.js.
The build configuration redirects imports to compiled bundles through moduleNameMapper, ignores internal test files, excludes build output from transforms, and adds setupTests.build.js. This is the boundary that lets Jest exercise built artifacts rather than only source modules.
| Configuration | Main selection | Additional setup |
|---|---|---|
| Source www | Shared base configuration with www-specific ignored packages | setupTests.www.js, setupHostConfigs.js |
| Source persistent | Shared base configuration with persistent-specific ignored tests | setupTests.persistent.js, setupHostConfigs.js |
| Source xplat | Shared base configuration | setupTests.xplat.js, setupHostConfigs.js |
| Build | Compiled-bundle module mapping | setupTests.build.js |
These configurations explain why setup files are part of the test contract: the selected environment determines both which modules Jest resolves and which initialization code runs before tests.
The flag set determines the active channel from __WWW__ and __EXPERIMENTAL__: Meta builds become modern or classic, while other builds become experimental or stable. It also marks whether the test is running from source using process.env.IS_BUILD.
The gate condition itself may be a string or function, but the shown helper does not hide the result: the string form directly indexes the flags object. Because getTestFlags throws for a missing flag, a misspelled gate name fails loudly instead of silently selecting a channel.
When the gated path expects failure, expectTestToFail treats either a rejected callback or a captured global error as the expected outcome; if the callback completes without error, it throws errorToThrowIfTestSucceeds. This is why a test can be marked as expected to fail when its feature flag is off: success under that channel is itself reported as a failure.
Sources: scripts/jest/config.source-persistent.js:1-23, scripts/jest/config.source-xplat.js:27-32, scripts/jest/config.build.js:57-75, scripts/jest/config.source-www.js:5-17, scripts/jest/TestFlags.js:56-126, scripts/jest/TestFlags.js:35-54, scripts/jest/setupTests.js:235-241, scripts/jest/TestFlags.js:114-123, scripts/jest/setupTests.js:181-233
How internal test utilities control work
waitFor first verifies that no prior Scheduler yields or console logs remain. It waits for a microtask, flushes only enough Scheduler yields to reach the expected sequence, clears those yields, and performs one additional microtask once the expected count is reached.
waitForAll uses the same clean-start requirement, but repeatedly calls SchedulerMock.unstable_flushAllWithoutAsserting until no work remains, then compares the complete cleared log.
The following workflow shows the common partial-versus-complete flushing path.
The workflow answers how an expected Scheduler log becomes an assertion.
Evidence
- wait-forpackages/internal-test-utils/ReactInternalTestUtils.js:43
- wait-for-allpackages/internal-test-utils/ReactInternalTestUtils.js:92
- microtaskspackages/internal-test-utils/ReactInternalTestUtils.js:37
- scheduler-mockpackages/internal-test-utils/ReactInternalTestUtils.js:43
- scheduler-mockpackages/internal-test-utils/ReactInternalTestUtils.js:92
- event-logpackages/internal-test-utils/ReactInternalTestUtils.js:43
- event-logpackages/internal-test-utils/ReactInternalTestUtils.js:92
assertLog is the immediate form: it clears the Scheduler mock once and compares the result, without waiting or flushing. The separate console helpers assert development logs, warnings, and errors through assertConsoleLogDev, assertConsoleWarnDev, and assertConsoleErrorDev.
Internal act differs from public act() in several deliberate ways. It requires a special mock build of Scheduler and fake timers, inserts an asynchronous gap before invoking the scope, disables React’s act warning environment for its outermost scope, and flushes pending ticks and timers to expose Suspense fallbacks. The timer loop is isolated in waitForTasksAndTimers, which throws if the Jest environment has been torn down, flushes pending ticks and timers while timers remain, and stops once no timers remain.
Errors raised globally during the internal scope are collected in thrownErrors and rethrown after the scope completes; multiple errors are combined by aggregateErrors. This lets DOM error events and Node uncaught exceptions participate in the same deterministic assertion boundary.
Sources: packages/internal-test-utils/ReactInternalTestUtils.js:25-35, packages/internal-test-utils/ReactInternalTestUtils.js:43-90, packages/internal-test-utils/ReactInternalTestUtils.js:92-121, packages/internal-test-utils/ReactInternalTestUtils.js:312-325, packages/internal-test-utils/ReactInternalTestUtils.js:327-331, packages/internal-test-utils/ReactInternalTestUtils.js:332-336, packages/internal-test-utils/ReactInternalTestUtils.js:337-341, packages/internal-test-utils/internalAct.js:42-195, packages/internal-test-utils/internalAct.js:197-228, packages/internal-test-utils/internalAct.js:27, packages/internal-test-utils/internalAct.js:35-40
How renderer forks are mapped
The reconciler and server packages expose five configuration paths through configPaths: the reconciler host config, Flight client config, server stream config, Fizz config, and Flight server config. mockAllConfigs installs a Jest mock for each path and searches under a forks directory using the renderer’s short name.
The mapping is progressive rather than a single exact lookup. For a renderer short name split on hyphens, the helper tries the full joined name, then removes trailing parts until fs.statSync finds an existing candidate; it then returns that candidate with jest.requireActual.
The setup also defines shim paths for the host, server-stream, server, and Flight configurations, while shimFlightClientConfigPath covers the client Flight configuration. These paths identify the module boundaries that Jest replaces before the reconciler imports them.
The following sequence shows the fork-selection hand-off.
The sequence answers how a renderer name reaches the selected host-config implementation.
Evidence
- config-pathsscripts/jest/setupHostConfigs.js:180
- mock-all-configsscripts/jest/setupHostConfigs.js:188
- renderer-infoscripts/jest/setupHostConfigs.js:188
- fork-filescripts/jest/setupHostConfigs.js:188
- loaded-configscripts/jest/setupHostConfigs.js:188
Entry-point resolution is a related but separate fork mechanism. resolveEntryFork chooses Facebook-specific modern, classic, or generic forks when available, otherwise tries experimental or stable forks, then development and plain entries. For Facebook react-dom entries, it aliases related entry points to the appropriate shared implementation.
Sources: scripts/jest/setupHostConfigs.js:180-186, scripts/jest/setupHostConfigs.js:188-213, scripts/jest/setupHostConfigs.js:137, scripts/jest/setupHostConfigs.js:144, scripts/jest/setupHostConfigs.js:145, scripts/jest/setupHostConfigs.js:146, scripts/jest/setupHostConfigs.js:172, scripts/jest/setupHostConfigs.js:7-99
How it connects
The test configuration connects Jest to Build, Test, and Feature Flags, because the build configuration redirects imports to compiled bundles while getTestFlags exposes channel and build state.
The internal Scheduler assertions connect to Testing with act() and Test Renderer and Scheduler Priorities & Task Queue: the helpers flush mocked work, compare yielded logs, and provide the internal act boundary used by renderer tests.
The renderer mapping connects to Rollup Bundles & Module Forks, because both Jest and the bundle system select renderer-specific fork files from renderer metadata and fork naming conventions.
Sources: scripts/jest/config.build.js:57-75, scripts/jest/TestFlags.js:56-126, packages/internal-test-utils/ReactInternalTestUtils.js:43-90, packages/internal-test-utils/ReactInternalTestUtils.js:92-121, packages/internal-test-utils/internalAct.js:42-195, scripts/jest/setupHostConfigs.js:188-213, scripts/rollup/forks.js:357-378
Key takeaways
- Jest selects source or built-bundle behavior through configuration-specific setup files and module mapping.
getTestFlagsderives release-channel state and rejects unknown feature flags.waitForflushes toward an expected Scheduler sequence;waitForAllflushes until no work remains;assertLogonly compares the current log.- Internal
actadds fake-timer handling, an asynchronous entry gap, and global error collection beyond public act(). mockAllConfigsmaps each renderer’s short name to the first existing host-config fork.