Jest Testing Guide

Learn how to structure tests, write powerful assertions, and mock dependencies with confidence.

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
    }
  }
}