skip to content

Jest — CLI and configuration

Day-to-day Jest CLI commands and config patterns — running tests, mocks, snapshots, coverage, watch mode, and migration tips.

6 min read 21 snippets deep dive

Jest — CLI and configuration#

What it is#

Jest is the long-incumbent JavaScript test runner: zero-config (mostly), built-in assertions, mocking, snapshots, and coverage. It still powers most legacy React, Next.js (pre-15), and large monorepos. For new Vite-based work, vitest is the modern alternative — same API, faster, ESM-native. This article focuses on day-to-day CLI use and config patterns assuming you already have Jest in the project.

Install#

npm install -D jest @types/jest

# TypeScript projects — pick a transformer
npm install -D @swc/jest          # SWC (10-20× faster, no type check)
# OR
npm install -D ts-jest             # ts-jest (slow, includes type check)

# DOM testing (since Jest v28)
npm install -D jest-environment-jsdom

Output: (none — exits 0)

Add scripts:

{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:ci": "jest --ci --coverage --reporters=default --reporters=jest-junit",
    "coverage": "jest --coverage"
  }
}

Day-to-day commands#

CommandWhat it does
jestRun every test file. Defaults to __tests__/** and *.test.{js,ts}.
jest path/to/file.test.tsRun one specific file.
jest --testNamePattern="adds two" (-t)Run tests whose name matches a regex.
jest --watchRe-run tests on file change. Uses git to find changed files.
jest --watchAllWatch every test file (no git heuristic).
jest --coverageGenerate coverage report. Defaults to coverage/.
jest --ciCI-friendly mode: never write snapshots automatically, exit non-zero on missing snapshots.
jest -u (--updateSnapshot)Update snapshot files in place.
jest --findRelatedTests src/foo.tsRun tests that import the given source files.
jest --shard=N/MPartition test files deterministically — for parallel CI jobs.
jest --detectOpenHandlesFind leaked timers / sockets after the suite ends.
jest --listTestsPrint resolved test files; don’t run anything.

Common scenarios#

Run a single test by name#

jest -t "uppercases the user name"

Output:

PASS  src/user.test.ts
  ✓ uppercases the user name (3 ms)

Tests:       1 passed, 1 total

-t is a regex against describe + it names. Quote it if it has spaces.

Watch mode with name filter#

jest --watch
# Press `p` to filter by filename, `t` to filter by test name

Output:

Watch Usage
 › Press a to run all tests.
 › Press f to run only failed tests.
 › Press p to filter by a filename regex pattern.
 › Press t to filter by a test name regex pattern.
 › Press q to quit watch mode.

The watch UI is the best in the runner space — interactive prompts beat re-typing flags.

Coverage with thresholds#

// jest.config.js
export default {
  collectCoverage: true,
  coverageDirectory: "coverage",
  coverageReporters: ["text", "lcov", "json-summary"],
  collectCoverageFrom: [
    "src/**/*.{ts,tsx}",
    "!src/**/*.d.ts",
    "!src/**/index.ts",
  ],
  coverageThreshold: {
    global: { branches: 75, functions: 80, lines: 80, statements: 80 },
  },
};
jest --coverage

Output:

Tests:       42 passed, 42 total
-------|---------|----------|---------|---------|
File   | % Stmts | % Branch | % Funcs | % Lines |
-------|---------|----------|---------|---------|
All    |   84.21 |    77.13 |   87.50 |   84.21 |
-------|---------|----------|---------|---------|

Exits non-zero if any metric falls below the floor — wire as a required PR check.

Snapshot testing#

test("renders banner HTML", () => {
  const html = renderToString(<Banner title="Hi" />);
  expect(html).toMatchSnapshot();
});

First run writes __snapshots__/banner.test.ts.snap. After intentional UI changes:

jest -u                              # update all snapshots
jest -u path/to/banner.test.ts       # update for one file

Output:

PASS  src/banner.test.ts
  › 1 snapshot updated.

Snapshots:   1 updated, 1 total
Tests:       1 passed, 1 total

For tiny outputs, prefer inline snapshots:

expect(formatPrice(99.5)).toMatchInlineSnapshot(`"$99.50"`);

Mocking modules#

jest.mock is hoisted above imports — factory closures can’t reference uninitialised local variables:

// src/user.test.ts
import { jest } from "@jest/globals";
import { fetchUser } from "./api.js";
import { getUserName } from "./user.js";

jest.mock("./api.js");

const mockedFetch = jest.mocked(fetchUser);

beforeEach(() => mockedFetch.mockReset());

test("uppercases name", async () => {
  mockedFetch.mockResolvedValue({ name: "alice" });
  expect(await getUserName(1)).toBe("ALICE");
});

jest.mocked() gives you a typed mock wrapper — much better than casting as jest.Mock.

Sharding across CI runners#

strategy:
  matrix:
    shard: [1, 2, 3, 4]
steps:
  - run: jest --shard=${{ matrix.shard }}/4 --ci

Each runner takes 25% of files; total wall time roughly quartered.

React Testing Library setup#

// jest.config.js
export default {
  testEnvironment: "jsdom",
  setupFilesAfterEach: ["<rootDir>/jest.setup.js"],
};
// jest.setup.js
import "@testing-library/jest-dom";
// Button.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Button } from "./Button";

test("calls onClick when clicked", async () => {
  const onClick = jest.fn();
  render(<Button onClick={onClick}>Click me</Button>);
  await userEvent.click(screen.getByRole("button"));
  expect(onClick).toHaveBeenCalledTimes(1);
});

Useful flags#

FlagPurpose
--bailStop after the first failure. Cuts CI time when most tests pass.
--maxWorkers=2Cap parallel workers — useful on memory-limited CI runners.
--maxWorkers=50%Half of available cores.
--runInBandDisable parallelism. Sequential. Useful for debugging order-dependent tests.
--silentSuppress console output from tests.
--verbosePrint every test name (good for debugging which test hung).
--clearCacheWipe Jest’s transform cache. Try if you get spurious errors after dep upgrades.
--config jest.config.ci.jsUse an alternate config — common for CI-specific overrides.
--reporters=default --reporters=jest-junitMultiple reporters (e.g. JUnit XML for CI dashboards).
--changedSince=mainOnly tests for files changed vs the given ref.

Configuration#

Jest loads config from (in order):

  1. jest key in package.json
  2. jest.config.js / .ts / .cjs / .mjs
  3. jest.config.json

Modern TS + React config#

// jest.config.ts
import type { Config } from "jest";

const config: Config = {
  testEnvironment: "jsdom",
  setupFilesAfterEach: ["<rootDir>/jest.setup.ts"],
  transform: {
    "^.+\\.(t|j)sx?$": ["@swc/jest", {
      jsc: { transform: { react: { runtime: "automatic" } } },
    }],
  },
  moduleNameMapper: {
    "\\.(css|less|scss)$": "identity-obj-proxy",
    "^@/(.*)$": "<rootDir>/src/$1",
  },
  testMatch: ["**/__tests__/**/*.test.{ts,tsx}", "**/?(*.)+(spec|test).{ts,tsx}"],
  collectCoverageFrom: ["src/**/*.{ts,tsx}", "!**/*.d.ts"],
};

export default config;

Multi-project (monorepo)#

// jest.config.js (root)
export default {
  projects: [
    "<rootDir>/packages/api",
    "<rootDir>/packages/web",
    "<rootDir>/packages/shared",
  ],
};

Each sub-project has its own jest.config.js. The root aggregates results — coverage and reports merge.

Per-file environment override#

/**
 * @jest-environment jsdom
 */
import { render } from "@testing-library/react";
// ...

Useful when most tests are Node but a handful need a DOM.

Globals via setupFiles vs setupFilesAfterEach#

HookWhen
setupFilesBefore Jest globals are available. Use for polyfills only.
setupFilesAfterEachAfter expect, jest, etc. are wired. Use for matcher extensions, global mocks.

Common pitfalls#

  1. jest-environment-jsdom not installed (v28+)Cannot find module 'jest-environment-jsdom'. Install it explicitly.
  2. ESM source with CJS JestSyntaxError: Cannot use import statement outside a module. Use @swc/jest or babel-jest, or enable experimental ESM with NODE_OPTIONS=--experimental-vm-modules.
  3. jest.mock reference error — referencing local variables in a jest.mock factory. Use jest.doMock after imports, or inline the value.
  4. Tests pass alone, fail together — global state leaking. Set resetModules: true and clearMocks: true in config.
  5. Jest did not exit one second after the test run — open handle (timer, server, DB pool). Run with --detectOpenHandles to find it.
  6. Watch mode does nothing — non-git directory or no committed git history. Use --watchAll instead.
  7. @swc/jest skips type checking — run tsc --noEmit separately, ideally as a parallel CI step.
  8. Coverage is 0% for some files — they’re not imported by any test, OR collectCoverageFrom excludes them, OR they’re in testPathIgnorePatterns.

See also#