# How the Test Runner Works with Jest in Create React App: A Deep Dive into react-scripts

> Understand how `react-scripts test` integrates Jest in Create React App. Learn about Jest configuration and watch mode for seamless testing within your projects.

- Repository: [Meta/create-react-app](https://github.com/facebook/create-react-app)
- Tags: deep-dive
- Published: 2026-02-26

---

**When you run `npm test` in a Create React App project, the `react-scripts test` command bootstraps Jest by setting environment variables, detecting your version control system for watch mode, and generating a zero-configuration Jest config on-the-fly before launching the test runner.**

Create React App (CRA) provides a zero-configuration testing experience powered by Jest. Understanding how the test runner works with Jest in Create React App helps developers debug configuration issues and leverage advanced features without ejecting. This article examines the internal machinery inside `react-scripts` that transforms a simple `npm test` command into a fully configured Jest execution.

## The Entry Point: react-scripts test Command

When you execute `npm test` in a CRA project, the [`package.json`](https://github.com/facebook/create-react-app/blob/main/package.json) script delegates to `react-scripts test`. This command is defined in [`packages/react-scripts/bin/react-scripts.js`](https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/bin/react-scripts.js) and ultimately executes [`packages/react-scripts/scripts/test.js`](https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/scripts/test.js).

The [`test.js`](https://github.com/facebook/create-react-app/blob/main/test.js) script acts as the orchestrator that prepares the runtime environment, assembles the Jest configuration, and invokes the Jest CLI.

## Environment Preparation Pipeline

Before Jest starts, the test runner performs several critical environment setup steps to ensure consistent behavior across different machines and CI environments.

### Forcing Test Environment Variables

The script explicitly sets three environment variables to `"test"`:

- `process.env.BABEL_ENV`
- `process.env.NODE_ENV`
- `process.env.PUBLIC_URL`

This ensures that Babel transpiles code with the appropriate presets and that any code branching on `NODE_ENV` behaves correctly during testing. This logic resides at the top of [`packages/react-scripts/scripts/test.js`](https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/scripts/test.js).

### Handling Unhandled Promise Rejections

To prevent silent failures in async tests, the script installs a process handler that converts unhandled promise rejections into thrown errors. This makes debugging easier by ensuring that unhandled rejections crash the test suite rather than logging warnings.

### Loading Environment Files

The script requires [`../config/env.js`](https://github.com/facebook/create-react-app/blob/main/../config/env.js) to load variables defined in `.env*` files (such as `.env.local` or `.env.test`). This makes `REACT_APP_*` variables available to your test code, matching the behavior of the development and production builds.

## Watch Mode Intelligence

CRA's test runner includes sophisticated logic to determine whether Jest should run in watch mode by default.

### Version Control Detection

Before launching Jest, the script checks whether the project resides inside a version control repository using two helper functions:

- `isInGitRepository()`
- `isInMercurialRepository()`

These functions are defined in [`packages/react-scripts/scripts/test.js`](https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/scripts/test.js) and use simple shell commands to detect `.git` or `.hg` directories.

If a VCS is detected and `CI` is not set to `true`, the runner defaults to watch mode. This provides a fast feedback loop during development while ensuring deterministic single runs in CI environments or when no version control is present.

## Jest Configuration Assembly

Rather than relying on a static [`jest.config.js`](https://github.com/facebook/create-react-app/blob/main/jest.config.js) file, CRA generates the Jest configuration programmatically at runtime.

### The createJestConfig Factory

The `createJestConfig` function in [`packages/react-scripts/scripts/utils/createJestConfig.js`](https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/scripts/utils/createJestConfig.js) constructs the configuration object. It performs the following:

- Sets `roots` to `<rootDir>/src`
- Configures `collectCoverageFrom` to include source files and exclude test files
- Defines `setupFiles` for polyfills and `setupFilesAfterEnv` for test setup
- **Auto-detects `src/setupTests.*`** files and includes them automatically
- Configures `transform` to use CRA's Babel transformer for JS/TS files
- Sets up `moduleNameMapper` to handle static assets (CSS, images, etc.)
- Includes default `watchPlugins` for interactive filtering

### Supported Override Whitelist

Users can customize Jest behavior by adding a `jest` key to their [`package.json`](https://github.com/facebook/create-react-app/blob/main/package.json). However, CRA enforces a whitelist of supported configuration keys. If you attempt to override unsupported options, `createJestConfig` throws a clear error message explaining which keys are allowed.

This design maintains the zero-config philosophy while providing escape hatches for common customization needs like coverage thresholds and collect patterns.

## Launching the Test Runner

Once the configuration is assembled, the script prepares the final arguments and invokes Jest.

### Resolving Jest Environment Workarounds

The script includes a `resolveJestDefaultEnvironment` helper that works around a known Jest bug where the default environment module isn't resolved correctly in certain contexts. It uses the `resolve` package to ensure the `jsdom` (or user-specified) environment path is absolute before passing it to Jest.

### Executing Jest

Finally, the script calls `jest.run(argv)` with the prepared arguments array, transferring control to Jest's CLI. At this point, Jest takes over, using the CRA-generated configuration to discover and execute test files.

## Practical Configuration Examples

Here are common patterns for working with the CRA test runner:

```bash

# Run tests in interactive watch mode (default)

npm test

```

```bash

# Run tests once (CI mode)

CI=true npm test

# Or explicitly disable watch

npm test -- --watchAll=false

```

```javascript
// src/setupTests.js - Automatically loaded before each test
import '@testing-library/jest-dom';

// Add custom matchers or global mocks here

```

```json
// package.json - Override supported Jest options
{
  "jest": {
    "collectCoverageFrom": [
      "src/**/*.{js,jsx,ts,tsx}",
      "!src/**/*.test.{js,jsx,ts,tsx}",
      "!src/index.{js,jsx,ts,tsx}"
    ],
    "coverageThreshold": {
      "global": {
        "branches": 80,
        "functions": 80,
        "lines": 80,
        "statements": 80
      }
    }
  }
}

```

```bash

# Run specific test file while keeping watch mode

npm test -- src/components/Button.test.js

```

```bash

# Use Node environment instead of JSDOM (faster for non-DOM tests)

npm test -- --env=node

```

## Summary

- **`npm test`** invokes `react-scripts test`, which acts as a wrapper around Jest located in [`packages/react-scripts/scripts/test.js`](https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/scripts/test.js).
- The runner forces `NODE_ENV`, `BABEL_ENV`, and `PUBLIC_URL` to `"test"` to ensure consistent transpilation and runtime behavior.
- It auto-detects Git or Mercurial repositories to enable watch mode by default, while CI environments trigger single-run execution.
- Configuration is generated programmatically via `createJestConfig` in [`packages/react-scripts/scripts/utils/createJestConfig.js`](https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/scripts/utils/createJestConfig.js), which auto-includes `src/setupTests.*` files and enforces a whitelist of user-overridable options.
- The runner works around Jest environment resolution bugs before delegating to `jest.run(argv)` to execute the test suite.

## Frequently Asked Questions

### How do I run tests once without watch mode?

Set the `CI` environment variable to `true` or pass the `--watchAll=false` flag. In [`packages/react-scripts/scripts/test.js`](https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/scripts/test.js), the runner checks for `process.env.CI` or explicit watch flags to determine whether to launch Jest in interactive watch mode or single-run mode.

### Can I customize Jest configuration without ejecting?

Yes, but only within the supported whitelist defined in [`createJestConfig.js`](https://github.com/facebook/create-react-app/blob/main/createJestConfig.js). You can add a `jest` key to your [`package.json`](https://github.com/facebook/create-react-app/blob/main/package.json) to override options like `collectCoverageFrom`, `coverageThreshold`, or `transform`. Attempting to set unsupported keys causes the runner to throw an error listing the allowed options.

### Why does watch mode only work in Git repositories?

The runner explicitly checks for version control using `isInGitRepository()` and `isInMercurialRepository()` helpers in [`test.js`](https://github.com/facebook/create-react-app/blob/main/test.js). If no VCS is detected and `CI` is not set, the runner defaults to `--watchAll=false` to prevent infinite hanging in environments like temporary directories or Docker containers without git history.

### How do I add custom Jest matchers or global setup code?

Create a file named [`src/setupTests.js`](https://github.com/facebook/create-react-app/blob/main/src/setupTests.js) (or `.ts`, `.tsx`). The `createJestConfig` function automatically detects this file pattern and adds it to `setupFilesAfterEnv`, ensuring it runs before each test file. This is the standard location for importing `@testing-library/jest-dom` or configuring global mocks.