Jest Reference: Config, Matchers, Mocking, Async Tests & Snapshots
Jest is the default test runner for JavaScript and TypeScript. It comes pre-configured with Create React App and Next.js. The three things that make Jest powerful: a built-in mocking system (no separate library), snapshot testing for catching unintended output changes, and inline coverage reports. If you’re starting a new project, consider Vitest instead — it’s faster and ESM-native. But if you’re maintaining an existing Jest codebase, these are the patterns you need.
1. Setup & Configuration
Install, jest.config.ts, TypeScript support, module aliases, and transform config
# npm install -D jest @types/jest
# For TypeScript: npm install -D ts-jest
# Or Babel: npm install -D babel-jest @babel/preset-typescript
# jest.config.ts:
import type { Config } from "jest";
const config: Config = {
preset: "ts-jest", // TypeScript + Jest with ts-jest
// Or: transform: { "^.+\\.tsx?$": "babel-jest" } with babel config
testEnvironment: "node", // "node" (default) | "jsdom" (browser-like)
// For React testing: testEnvironment: "jsdom"
// npm install -D jest-environment-jsdom
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/$1", // @ alias
"\\.css$": "identity-obj-proxy", // CSS Modules mock
"\\.(png|svg|jpg)$": "<rootDir>/__mocks__/fileMock.js", // asset mock
},
setupFilesAfterFramework: ["<rootDir>/jest.setup.ts"],
// jest.setup.ts: import "@testing-library/jest-dom";
collectCoverageFrom: [
"src/**/*.{ts,tsx}",
"!src/**/*.d.ts",
"!src/**/*.stories.tsx",
"!src/index.ts",
],
coverageThresholds: {
global: { branches: 80, functions: 80, lines: 80, statements: 80 },
},
};
export default config;
# Run tests:
npx jest # all tests
npx jest --watch # watch mode
npx jest --coverage # coverage report
npx jest src/utils.test.ts # specific file
npx jest -t "adds numbers" # match test name
2. Matchers & Assertions
expect() matchers, custom matchers, and the most useful assertion patterns
// Core matchers:
expect(value).toBe(42); // strict equality (===)
expect(obj).toEqual({ a: 1 }); // deep equality (structure)
expect(str).toContain("hello"); // string or array contains
expect(arr).toHaveLength(3);
expect(fn).toThrow("error message");
expect(fn).toThrow(TypeError);
// Truthiness:
expect(val).toBeTruthy();
expect(val).toBeFalsy();
expect(val).toBeNull();
expect(val).toBeUndefined();
expect(val).toBeDefined();
// Numbers:
expect(n).toBeGreaterThan(10);
expect(n).toBeGreaterThanOrEqual(10);
expect(n).toBeLessThan(10);
expect(n).toBeCloseTo(3.14, 2); // floating point (2 decimal places)
// Objects and arrays:
expect(obj).toMatchObject({ a: 1 }); // subset match (extra props ok)
expect(arr).toContainEqual({ id: 1 }); // array contains object (deep)
expect(obj).toHaveProperty("user.name", "Alice"); // nested property
// Not:
expect(val).not.toBe(null);
expect(arr).not.toContain("bad value");
// Async:
await expect(promise).resolves.toBe("value");
await expect(promise).rejects.toThrow("error");
// jest-dom matchers (React Testing Library):
expect(element).toBeInTheDocument();
expect(element).toBeVisible();
expect(element).toBeDisabled();
expect(element).toHaveTextContent("Submit");
expect(input).toHaveValue("alice@example.com");
expect(button).toHaveClass("btn-primary");
3. Mocking
jest.fn(), jest.spyOn(), module mocks, and timer mocks
// jest.fn() — mock function:
const mockFn = jest.fn();
mockFn("hello");
expect(mockFn).toHaveBeenCalledWith("hello");
expect(mockFn).toHaveBeenCalledTimes(1);
// Mock return values:
const mockFetch = jest.fn()
.mockResolvedValueOnce({ data: [1, 2, 3] }) // first call
.mockResolvedValueOnce({ data: [4, 5, 6] }) // second call
.mockRejectedValueOnce(new Error("Network")); // third call throws
// jest.spyOn() — spy on real module method:
const spy = jest.spyOn(console, "error").mockImplementation(() => {});
// ... run code that calls console.error ...
expect(spy).toHaveBeenCalledWith("something went wrong");
spy.mockRestore(); // restore original implementation
// Module mock (__mocks__ folder or jest.mock()):
jest.mock("../api/users", () => ({
fetchUser: jest.fn().mockResolvedValue({ id: "1", name: "Alice" }),
}));
// jest.mock() is hoisted to top of file automatically
// Mock only specific export:
jest.mock("../utils", () => ({
...jest.requireActual("../utils"), // keep real implementations
randomId: jest.fn().mockReturnValue("test-id"), // override one
}));
// Timer mocks (fake timers):
jest.useFakeTimers();
const callback = jest.fn();
setTimeout(callback, 1000);
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalledTimes(1);
jest.useRealTimers(); // restore in afterEach
4. Async Tests & Setup/Teardown
async/await tests, beforeEach/afterAll lifecycle hooks, and test isolation
// Async tests:
it("fetches user", async () => {
const user = await fetchUser("1");
expect(user.name).toBe("Alice");
});
// Using done callback (legacy — prefer async/await):
it("calls callback", (done) => {
fetchUser("1", (user) => {
expect(user.name).toBe("Alice");
done(); // signal test is complete
});
});
// Lifecycle hooks:
describe("UserService", () => {
let db: TestDatabase;
beforeAll(async () => {
db = await TestDatabase.create(); // once for all tests in describe
});
afterAll(async () => {
await db.close();
});
beforeEach(() => {
db.reset(); // fresh state before each test
});
afterEach(() => {
jest.clearAllMocks(); // clear mock call counts between tests
// jest.resetAllMocks() also resets return values
// jest.restoreAllMocks() also restores spyOn implementations
});
});
// each — parameterized tests:
it.each([
[1, 2, 3],
[0, 0, 0],
[-1, 1, 0],
])("adds %i + %i = %i", (a, b, expected) => {
expect(add(a, b)).toBe(expected);
});
// skip and only:
it.skip("not ready yet", () => { ... });
it.only("focus on this", () => { ... }); // only run this test in file
5. Snapshots & Coverage
toMatchSnapshot(), inline snapshots, updating snapshots, and coverage thresholds
// Snapshot testing — captures output, fails if it changes:
it("renders correctly", () => {
const tree = render(<Button variant="primary">Click</Button>);
expect(tree.container.firstChild).toMatchSnapshot();
});
// First run: creates __snapshots__/Button.test.tsx.snap
// Future runs: compares against saved snapshot
// Update snapshot: npx jest --updateSnapshot (or -u)
// Inline snapshot (stored in test file — great for small values):
it("formats date", () => {
expect(formatDate(new Date("2024-01-15"))).toMatchInlineSnapshot(
`"January 15, 2024"`
);
});
// When to use snapshots:
// GOOD: UI component output, serialized data structures, error messages
// BAD: large objects (hard to review), frequently changing data, generated IDs
// Coverage:
// npx jest --coverage → generates coverage/lcov-report/index.html
// Coverage types: statements, branches, functions, lines
// CI coverage gate (jest.config.ts):
coverageThreshold: {
global: { lines: 80, branches: 70 }, // fail CI if below these
"./src/critical/": { lines: 95 }, // stricter for critical paths
},
// Exclude files from coverage:
collectCoverageFrom: [
"src/**/*.{ts,tsx}",
"!**/*.stories.{ts,tsx}", // Storybook stories
"!**/*.d.ts",
"!**/index.ts", // re-export barrels
],
Track Node.js and testing toolchain releases at ReleaseRun. Related: Vitest Reference | Playwright Reference | React Testing Library Reference
🔍 Free tool: npm Package Health Checker — check Jest, ts-jest, and testing utility packages for known CVEs and active maintenance.
Founded
2023 in London, UK
Contact
hello@releaserun.com