Testland
Browse all skills & agents

jest-tests

Configures and runs Jest - Meta-built batteries-included JS/TS unit framework with built-in `expect`, snapshot testing, mocking (`jest.mock`, `jest.fn`, `jest.spyOn`, manual `__mocks__/`), test environment selection (`jsdom` / `node`), parallel workers, coverage via Istanbul, watch mode, and CI integration via `--ci` flag. Use when the user works with React (CRA / older Next.js) or Node services and needs the most ecosystem-supported JS test framework.

Install with skills.sh (any agent)

npx skills add testland/qa --skill jest-tests
View source

jest-tests

Overview

Per jestjs.io/docs/getting-started (opens in new window):

Jest is "a delightful JavaScript Testing Framework with a focus on simplicity." It bundles expect assertions, snapshot testing, mocking (no separate Sinon needed), and code coverage in one tool. Works with TypeScript via babel-jest (faster) or ts-jest (full type-checking).

This skill targets per-framework lifecycle (configure / run / mock / coverage / CI) - NOT test code hygiene patterns. For hygiene (assertion quality / AAA structure / mocking anti-patterns), see test-code-conventions; test code is reviewed separately.

When to use

  • The repo has jest.config.js, jest.config.ts, or jest key in package.json.
  • The team works with React (CRA legacy + many React Native projects) or Node services.
  • Need built-in mocking + snapshot testing without external libs.
  • Migrating from a fragmented Mocha + Chai + Sinon + nyc setup.

For Vite-based projects, prefer vitest-tests (Vite-native; faster transform-pipeline reuse).

How to use

  1. Confirm Jest fits (React CRA / React Native / Node service, or a fragmented Mocha + Chai + Sinon setup to consolidate); for Vite projects prefer vitest-tests.
  2. Install jest (add ts-jest or babel-jest for TypeScript) and scaffold config with npm init jest@latest.
  3. Write *.test.js / *.spec.js files using expect matchers; wire "test": "jest" and run npm test.
  4. Replace collaborators with jest.fn / jest.mock / jest.spyOn and control time with fake timers (see Mocking).
  5. Set testEnvironment explicitly and add coverageThreshold to gate coverage; run jest --coverage (see Configuration and Coverage, CI, and ESLint).
  6. In CI run npx jest --ci --maxWorkers=2 with jest-junit for JUnit XML (see Coverage, CI, and ESLint).

Step 1 - Install

Per jest-start (opens in new window):

npm install --save-dev jest
# or yarn add --dev jest / pnpm add --save-dev jest / bun add --dev jest

For TypeScript, choose one:

# Option A: ts-jest (full type-checking; slower)
npm install --save-dev ts-jest

# Option B: babel-jest (faster; type errors NOT caught - pair with tsc --noEmit in CI)
npm install --save-dev babel-jest @babel/core @babel/preset-env @babel/preset-typescript

Per jest-start (opens in new window) babel.config.js for TS via Babel:

module.exports = {
  presets: [
    ['@babel/preset-env', {targets: {node: 'current'}}],
    '@babel/preset-typescript',
  ],
};

For type definitions, prefer the bundled @jest/globals:

npm install --save-dev @jest/globals

Then import explicitly per jest-start (opens in new window):

import {describe, expect, test} from '@jest/globals';
import {sum} from './sum';

This avoids global pollution + is the modern recommendation.

Step 2 - First test

Per jest-start (opens in new window):

// sum.js
function sum(a, b) {
  return a + b;
}
module.exports = sum;
// sum.test.js
const sum = require('./sum');

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

Wire package.json:

{
  "scripts": {
    "test": "jest"
  }
}

Run via npm test.

Worked example

A Node service function getUser(id) calls fetchUser from ./api-client; verify it without a live API.

// user-service.test.js
import { getUser } from './user-service';
import { fetchUser } from './api-client';

jest.mock('./api-client');

test('returns the fetched user', async () => {
  fetchUser.mockResolvedValue({ id: 1, name: 'Alice' });
  await expect(getUser(1)).resolves.toEqual({ id: 1, name: 'Alice' });
  expect(fetchUser).toHaveBeenCalledWith(1);
});

Run npm test. jest.mock('./api-client') auto-replaces the module so no network call fires; the test passes when getUser forwards the id and returns the resolved user.

Configuration

npm init jest@latest scaffolds a config. Key gotcha: testEnvironment defaults to jsdom in Jest 26 and earlier but node from Jest 27+, so set it explicitly. The full jest.config.js reference (coverage collection, moduleNameMapper path aliases, transform) is in references/configuration.md.

Mocking

Jest ships mocking without a separate Sinon: jest.fn() (standalone), jest.mock('./module') (automatic module mock), jest.spyOn(obj, 'method') (wrap an existing method), manual mocks in __mocks__/, and fake timers (jest.useFakeTimers). Patterns and timer control are in references/mocking.md.

Coverage, CI, and ESLint

Run jest --coverage (Istanbul) and gate via coverageThreshold. In CI, --ci fails on missing snapshots instead of writing them and disables interactive prompts; pair with --maxWorkers=2 on hosted runners and jest-junit for JUnit XML. The coverage config, GitHub Actions workflow, and ESLint test-globals setup are in references/coverage-ci-eslint.md.

Anti-patterns

Anti-patternWhy it failsFix
jest.mock at top of file without specific test scopeModule mock leaks across tests; brittlejest.doMock per-test or move to __mocks__/ (see Mocking)
--watchAll in CIHangs foreverUse --ci (see Coverage, CI, and ESLint)
Snapshot-only assertionsTests pass on every change without semantic verificationTargeted expect() for invariants; snapshots for stable shape only
Skip --maxWorkers config in CIDefault = #cores; can OOM CI runnersPin --maxWorkers=2 for typical hosted CI (see Coverage, CI, and ESLint)
Run TypeScript via babel-jest without separate tsc --noEmitType errors silently bypass testsPair babel-jest with tsc --noEmit in CI (Step 1)

Limitations

  • Slower than vitest-tests on Vite-based projects (Jest doesn't share dev-server transform).
  • Snapshot testing brittle at scale; pair with jest-image-snapshot for visual snapshots.
  • ESM (ECMAScript Modules) support has rough edges - many projects still use CommonJS for tests.
  • Module hoisting (jest.mock at top of file) has subtle ordering semantics; test code hygiene addressed in qa-test-review.

References

  • jest-start (opens in new window) - install, basic patterns, TS, ESLint
  • jestjs.io/docs/configuration - full config reference
  • jestjs.io/docs/cli - CLI reference incl. --ci
  • jestjs.io/docs/mock-functions - mocking deep dive
  • jestjs.io/docs/configuration#coverageThreshold-object - coverage gating
  • vitest-tests, mocha-tests, ava-tests, jasmine-tests - sister tools
  • test-code-conventions (qa-test-review) - test code hygiene (separate from per-framework lifecycle)

Jest configuration

Deep reference for jest-tests SKILL.md. Consult when generating or tuning jest.config.js - test environment, coverage collection, path aliases.

Generate config (per jest-start (opens in new window)):

npm init jest@latest

Common jest.config.js settings:

module.exports = {
  testEnvironment: 'jsdom',          // 'jsdom' for browser; 'node' for backend
  setupFilesAfterEach: ['<rootDir>/jest.setup.js'],
  testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],
  transform: {
    '^.+\\.(ts|tsx)$': 'ts-jest',
  },
  collectCoverageFrom: ['src/**/*.{js,ts}', '!src/**/*.d.ts'],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80,
    },
  },
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',  // path aliases matching tsconfig
  },
};

testEnvironment defaults to jsdom in Jest 26 and earlier; from Jest 27+ defaults to node. Set explicitly to avoid surprise.

Jest coverage, CI, and ESLint

View source (opens in new window)

Jest coverage, CI, and ESLint

Deep reference for jest-tests SKILL.md. Consult when wiring coverage gating, CI runs (--ci), JUnit reporting, or the ESLint test-globals config.

Coverage

jest --coverage

Output formats: text, lcov, html, json, json-summary. Configure via coverageReporters in jest.config.js. The coverageThreshold field fails the run if coverage drops below thresholds.

For the coverageThreshold per-file pattern:

coverageThreshold: {
  './src/critical-module/': {
    branches: 95,
    statements: 95,
  },
  './src/legacy/': {
    branches: 50,
  },
},

CI integration

Per Jest CLI, --ci flag is critical for CI runs:

# .github/workflows/test.yml
- run: npm ci
- run: npx jest --ci --coverage --maxWorkers=2 --reporters=default --reporters=jest-junit
- uses: codecov/codecov-action@v4
  with: { files: ./coverage/lcov.info }

--ci semantics:

  • Disables snapshot writing on missing snapshots (fails instead - prevents accidental snapshot generation in CI)
  • Disables interactive prompts
  • Equivalent to process.env.CI=true

--maxWorkers=2 is typical for GitHub-hosted runners (2 CPUs); tune per runner specs.

For JUnit XML output (consumable by junit-xml-analysis in qa-test-reporting):

npm install --save-dev jest-junit
JEST_JUNIT_OUTPUT_FILE=./test-results/junit.xml \
  jest --ci --reporters=default --reporters=jest-junit

ESLint integration

Per jest-start (opens in new window):

// eslint.config.js
import {defineConfig} from 'eslint/config';
import globals from 'globals';

export default defineConfig([
  {
    files: ['**/*.test.js', '**/*.spec.js'],
    languageOptions: {
      globals: { ...globals.jest },
    },
  },
]);

Or via eslint-plugin-jest:

npm install --save-dev eslint-plugin-jest
{
  "overrides": [{
    "files": ["**/*.test.js", "**/*.spec.js"],
    "plugins": ["jest"],
    "extends": ["plugin:jest/recommended"]
  }]
}

Jest mocking and fake timers

View source (opens in new window)

Jest mocking and fake timers

Deep reference for jest-tests SKILL.md. Consult for the three mock forms, manual __mocks__/, and fake-timer control. This is per-framework mocking lifecycle, not mocking hygiene (for anti-patterns see test-code-conventions).

Three forms:

// jest.fn() - standalone mock function
const myMock = jest.fn();
myMock.mockReturnValue(42);
expect(myMock(5)).toBe(42);
expect(myMock).toHaveBeenCalledWith(5);

// jest.mock('./module') - automatic module mock
jest.mock('./api-client');
import { fetchUser } from './api-client';
fetchUser.mockResolvedValue({ id: 1, name: 'Alice' });

// jest.spyOn(obj, 'method') - wrap existing method
const spy = jest.spyOn(myObject, 'someMethod')
  .mockImplementation(() => 'mocked');
expect(myObject.someMethod()).toBe('mocked');
spy.mockRestore();

Manual mocks live in __mocks__/ adjacent to the module:

src/
  api-client.js
  __mocks__/
    api-client.js   # automatically used when jest.mock('./api-client') runs

Timer mocks:

jest.useFakeTimers();
setTimeout(callback, 1000);
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalled();
jest.useRealTimers();

Related skills

ava-tests

Configures and runs AVA - concurrent-by-default JS/TS test framework with isolated test files (each file runs in its own Node process), no globals (explicit `import test from 'ava'`), async-first API, snapshot support, and TypeScript via `@ava/typescript`. Use when AVA is already the chosen framework and the user wants minimal-API parallel-by-default tests, works with libraries (vs apps) where per-file isolation prevents test interference, or is switching from Mocha for per-file process isolation Mocha cannot provide. For choosing between AVA and Mocha, or for Mocha-specific work, use mocha-tests.

jasmine-tests

Configures and runs Jasmine - the original BDD-style JS test framework (predecessor to Jest) with built-in matchers + spies, no external assertion library; ships `jasmine-core` + `jasmine` runner; `spec_dir` + `helpers` convention; `jasmine.json` config; spy patterns (`spyOn`, `createSpy`); pairs with Karma for in-browser testing. Use when the user maintains legacy AngularJS / Karma+Jasmine codebases, or wants minimal BDD-style tests with no third-party assertion library.

mocha-tests

Configures and runs Mocha - pluggable JS test runner pairable with Chai assertions, Sinon mocking, and nyc / c8 coverage; supports BDD interface (`describe` / `it`) and TDD interface (`suite` / `test`); async tests via callbacks / promises / async-await; `--parallel` mode (Mocha 8+); `.mocharc.json` config; per-test exclusivity via `.only()` / `.skip()`. Use when the user prefers a minimal pluggable runner (vs Jest's batteries-included) or maintains legacy Mocha codebases.

vitest-tests

Configures and runs Vitest - Vite-native unit framework with Jest-compatible API (`expect`, `vi.fn`, `vi.mock`, `vi.spyOn`); reads `vite.config.*` so existing Vite plugins work; supports in-source testing via `if (import.meta.vitest)`, browser-mode UI for headed tests, type-checking via `vitest --typecheck`, native ESM, and coverage via v8 (default) or istanbul providers. Use when the user works with Vite-based projects (Vue, Svelte, Solid, modern React with Vite) or is migrating from Jest on an existing Vite project (not bundler-free Node - use jest-tests for that).