Overview
What is Jest?
Jest is a JavaScript test runner that bundles assertions, mocking, and coverage in one tool. It is fast, runs in Node, and ships with a great developer experience out of the box.
# Install Jest
npm install -D jest
# Run tests
npx jest
Key Point: Jest treats files with *.test.ts, *.spec.ts, and __tests__ folders as test suites by default.
Structure
Test Structure (AAA)
Follow Arrange-Act-Assert for clear and maintainable tests. Keep setup, action, and expectation separated to make failures obvious.
// sum.test.ts
import { sum } from "./sum";
describe("sum", () => {
it("adds two numbers", () => {
// Arrange
const a = 2;
const b = 3;
// Act
const result = sum(a, b);
// Assert
expect(result).toBe(5);
});
});
Assertions
Common Matchers
expect(2 + 2).toBe(4);
expect(["a", "b"]).toContain("a");
expect({ name: "Ada" }).toEqual({ name: "Ada" });
expect("hello").toMatch(/ell/);
expect(() => JSON.parse("bad")).toThrow();
expect(Promise.resolve("ok")).resolves.toBe("ok");
Key Point: Use toEqual for deep equality, and toBefor reference or primitive equality.
Mocking
Mock Functions & Spies
Mock functions track calls and return values. Spies wrap real functions so you can assert usage without replacing behavior.
const fetchUser = jest.fn().mockResolvedValue({ id: 1, name: "Ada" });
await fetchUser();
expect(fetchUser).toHaveBeenCalledTimes(1);
expect(fetchUser).toHaveBeenCalledWith();
const logger = { info: (msg: string) => msg };
const spy = jest.spyOn(logger, "info");
logger.info("hello");
expect(spy).toHaveBeenCalledWith("hello");
spy.mockRestore();
Modules
Module Mocking Patterns
// userService.ts
export async function getUser() {
const res = await fetch("/api/user");
return res.json();
}
// userService.test.ts
jest.mock("./userService", () => ({
getUser: jest.fn(),
}));
import { getUser } from "./userService";
(getUser as jest.Mock).mockResolvedValue({ id: 1, name: "Ada" });
expect(await getUser()).toEqual({ id: 1, name: "Ada" });
Key Point: Use jest.requireActual to keep real exports and mock only specific functions when needed.
API Mocking
Mock APIs with MSW
Mock Service Worker (MSW) intercepts network requests so you can test real fetch logic without hitting live services. It works for unit tests and integration tests alike.
// test/mocks/handlers.ts
import { rest } from "msw";
export const handlers = [
rest.get("/api/user", (_req, res, ctx) => {
return res(ctx.status(200), ctx.json({ id: 1, name: "Ada" }));
}),
];
// test/mocks/server.ts
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
export const server = setupServer(...handlers);
// test/setup.ts
import { server } from "./mocks/server";
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
Async
Testing Async Code
it("resolves with data", async () => {
const result = await fetchData();
expect(result).toEqual({ ok: true });
});
it("rejects with error", async () => {
await expect(fetchData()).rejects.toThrow("Network error");
});
Timers
Fake Timers
jest.useFakeTimers();
const callback = jest.fn();
setTimeout(callback, 1000);
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalled();
jest.useRealTimers();
Setup
Setup & Teardown
beforeAll(() => {
// Runs once before all tests
});
beforeEach(() => {
// Runs before each test
});
afterEach(() => {
// Cleanup after each test
});
afterAll(() => {
// Runs once after all tests
});
Key Point: Use a global setup file (like jest.setup.ts) for shared configuration and test utilities.
Snapshots
Snapshot Testing
import { render } from "@testing-library/react";
import Button from "./Button";
it("matches snapshot", () => {
const { container } = render(<Button label="Save" />);
expect(container.firstChild).toMatchSnapshot();
});
Coverage
Code Coverage
# Generate a coverage report
npx jest --coverage
# Enforce minimum thresholds in package.json
"jest": {
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}