Skip to content

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