Open-Source Wikis

/

React

/

How to contribute

/

Testing

facebook/react

Testing

The runtime test suite is Jest, with substantial wrapping, environment configuration, and feature-flag handling provided by scripts/jest/. Tests run hundreds of thousands of times across release channels in CI; learning the gating system is the difference between "I added a test for my fix" and "my test was silently skipped on the channel I cared about".

Running tests

yarn test                                  # default: experimental channel, source mode
yarn test-stable                           # stable channel
yarn test-www                              # www-modern
yarn test-classic                          # www-classic
yarn test --build                          # run against build/ bundles instead of source
yarn test packages/react-reconciler/...    # focused
yarn test -t "useState"                    # pattern by description
yarn test-build-devtools                   # DevTools-specific run, experimental channel

The runner is scripts/jest/jest-cli.js, which translates flags to the correct Jest config under scripts/jest/config.source*.js or scripts/jest/config.build*.js.

Where tests live

Each package has a src/__tests__/ directory next to its source. The reconciler — being renderer-agnostic — has the largest set of tests, all written against react-noop-renderer so they exercise the work loop without DOM dependencies. Examples:

  • packages/react-reconciler/src/__tests__/ReactHooks-test.js
  • packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
  • packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js
  • packages/react-server-dom-webpack/src/__tests__/
  • packages/react-devtools-shared/src/__tests__/store-test.js

Files ending -test.internal.js may use jest.mock to flip private feature flags. Files ending -test.js should not.

Feature-flag gating

Every test runs against every release channel by default. To make a test conditional on a flag, use the @gate pragma or the gate runtime helper. Both are set up in scripts/jest/setupTests.js.

// @gate enableNewFeature
it('does something the new way', () => {
  /* ... */
});

// Inside a test:
test('mixed', () => {
  if (gate((flags) => flags.enableNewFeature)) {
    // expect new behavior
  } else {
    // expect old behavior
  }
});

@gate experimental and @gate !www are also supported. When a test is gated off, Jest still runs it — but reverses the pass/fail expectation, ensuring the gated code path is never silently skipped on a channel where it should run.

Writing renderer-agnostic tests

When fixing a reconciler bug, prefer react-noop-renderer:

const Scheduler = require('scheduler/unstable_mock');
const ReactNoop = require('react-noop-renderer');
const React = require('react');

it('schedules an update', async () => {
  ReactNoop.render(<MyComponent />);
  await waitForAll([]);
  expect(ReactNoop).toMatchRenderedOutput(<span>hi</span>);
});

The helpers waitForAll, waitForPaint, waitFor, etc. come from internal-test-utils (packages/internal-test-utils/). They wrap act and the scheduler's mock to give you a synchronous-feeling test of an async render.

Snapshot rules

The runtime largely avoids Jest snapshot tests in favor of explicit assertions: toEqual, toMatchRenderedOutput, etc. The compiler is the opposite — it leans heavily on golden-file snapshots in compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/ and uses its own runner (yarn snap from inside compiler/).

Update compiler snapshots:

cd compiler
yarn snap -u           # update everything
yarn snap -u -p simple # update just files matching the pattern

Testing across renderers

Some bugs only manifest in react-dom. The DOM test files live in packages/react-dom/src/__tests__/, and they use jsdom (configured in scripts/jest/preprocessor.js and friends). For SSR/Fizz tests, look in packages/react-dom/src/__tests__/ReactDOMFizz*-test.js.

Server Components are tested in:

  • packages/react-server-dom-webpack/src/__tests__/
  • packages/react-server-dom-parcel/src/__tests__/
  • packages/react-server-dom-turbopack/src/__tests__/
  • packages/react-server-dom-unbundled/src/__tests__/

These are bundler-aware tests and use Jest module mocks to stand in for the bundler's manifest.

Mocking time and the scheduler

The scheduler comes with a unstable_mock implementation that lets a test step the work loop one frame at a time:

const Scheduler = require('scheduler/unstable_mock');

Scheduler.unstable_advanceTime(100);
Scheduler.unstable_flushAll();
expect(Scheduler).toFlushAndYield(['A', 'B']);

This is essential for testing concurrent features (transitions, deferred values, suspense retries). All the tests in packages/react-reconciler/src/__tests__/ use it.

Fuzz tests

yarn test-fuzz runs packages/react-reconciler/src/__tests__/ReactSuspenseFuzz-test.internal.js and a couple of sibling files. CI runs them on a separate workflow (runtime_fuzz_tests.yml) so a long fuzz run doesn't block normal PRs.

Common gotchas

  • act warnings. If you forget to wrap an update in act in a react-dom test, the warning is loud. Use waitFor* from internal-test-utils to keep things tidy.
  • Channel-specific failures. A test that passes locally on experimental can fail on stable if it relies on a flag-gated API. Always run the appropriate yarn test-stable / yarn test-www / yarn test-classic before assuming you're done.
  • -test.internal.js vs -test.js. Internal tests can jest.mock private feature flags. Non-internal tests cannot — and forgetting that is the most common cause of "I can't get my test to fail".

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Testing – React wiki | Factory