From qa-unit-tests-js
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.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qa-unit-tests-js:jest-testsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Per [jestjs.io/docs/getting-started][jest-start]:
Per jestjs.io/docs/getting-started:
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
and the qa-test-review agents.
jest.config.js, jest.config.ts, or jest key in
package.json.For Vite-based projects, prefer vitest-tests
(Vite-native; faster transform-pipeline reuse).
Per jest-start:
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 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:
import {describe, expect, test} from '@jest/globals';
import {sum} from './sum';
This avoids global pollution + is the modern recommendation.
Per jest-start:
// 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.
Generate config (per jest-start):
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.
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();
jest --coverage
Output formats: text, lcov, html, json, json-summary. Configure via
coverageReporters in jest.config.js. The coverageThreshold
field (Step 3) 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,
},
},
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:
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):
npm install --save-dev jest-junit
JEST_JUNIT_OUTPUT_FILE=./test-results/junit.xml \
jest --ci --reporters=default --reporters=jest-junit
Per jest-start:
// 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"]
}]
}
| 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__/ (Step 4) |
--watchAll in CI | Hangs forever | Use --ci (Step 6) |
| 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 (Step 6) |
Run TypeScript via babel-jest without separate tsc --noEmit | Type errors silently bypass tests | Pair babel-jest with tsc --noEmit in CI (Step 1) |
vitest-tests on
Vite-based projects (Jest doesn't share dev-server transform).jest-image-snapshot
for visual snapshots.jest.mock at top of file) has subtle ordering
semantics; test code hygiene addressed in qa-test-review.--civitest-tests,
mocha-tests,
ava-tests,
jasmine-tests - sister toolstest-code-conventions - cross-plugin: test code hygiene (separate from per-framework
lifecycle)npx claudepluginhub testland/qa --plugin qa-unit-tests-jsProvides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.