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-testsjest-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
For Vite-based projects, prefer vitest-tests (Vite-native; faster transform-pipeline reuse).
How to use
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 jestFor 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-typescriptPer 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/globalsThen 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-pattern | Why it fails | Fix |
|---|---|---|
jest.mock at top of file without specific test scope | Module mock leaks across tests; brittle | jest.doMock per-test or move to __mocks__/ (see Mocking) |
--watchAll in CI | Hangs forever | Use --ci (see Coverage, CI, and ESLint) |
| Snapshot-only assertions | Tests pass on every change without semantic verification | Targeted expect() for invariants; snapshots for stable shape only |
Skip --maxWorkers config in CI | Default = #cores; can OOM CI runners | Pin --maxWorkers=2 for typical hosted CI (see Coverage, CI, and ESLint) |
Run TypeScript via babel-jest without separate tsc --noEmit | Type errors silently bypass tests | Pair babel-jest with tsc --noEmit in CI (Step 1) |
Limitations
References
Jest configuration
View source (opens in new window)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@latestCommon 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 --coverageOutput 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:
--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-junitESLint 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') runsTimer 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).