DS DevShelfHub Projects · AI tools
Cheatsheets / Jest
Cheatsheet · Dev tooling

Jest: Matchers, Mocks, Spies, Async and Snapshot Testing Reference Guide

By DevShelfHub

Matchers, mocks, spies, snapshots, async, timers, coverage, modules — the day-to-day Jest 29+ surface.

110 items 8 min Matchers Mocks Snapshots

Start hereQuick start · 6 you’ll reach for daily

Run allnpx jest
Name filterjest -t "login"
Watchjest --watch
Update snapshotsjest -u
Mock a fnjest.fn()
Coveragejest --coverage

Target versions · paceVersions

Targets: jest ≥ 29 node ≥ 18 ts-jest ≥ 29 @testing-library/react ≥ 14

Jest 29 dropped Node 12/14 and moved most globals (jest, expect, lifecycle hooks) behind @jest/globals for ESM friendliness — still injected by default, but import them explicitly in TS/ESM projects. ESM support is opt-in via --experimental-vm-modules. Prefer ts-jest over Babel for TS unless you already have Babel; for Next/Vite codebases, Vitest is the modern peer (same API surface, faster ESM-native runner).

Install · runSetup

bash
# Install (npm / pnpm / yarn — pick one)
npm i -D jest @types/jest ts-jest @jest/globals
# For React or DOM tests
npm i -D jest-environment-jsdom @testing-library/jest-dom @testing-library/react

# Init a jest.config.* (interactive)
npx jest --init

# package.json
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov":   "jest --coverage"
  }
}

# Run
npx jest                              # all tests
npx jest path/to/file.test.ts         # one file
npx jest -t "login"                   # name filter (regex)
npx jest --watch                      # rerun on file change (uses git)
npx jest --watchAll                   # rerun on any change
npx jest --updateSnapshot             # -u, accept current output as truth
npx jest --runInBand                  # serial (debug / shared state)
npx jest --coverage --collectCoverageFrom='src/**/*.{ts,tsx}'
npx jest --detectOpenHandles          # find async leaks

Where things liveCommon imports

Globals (test, expect, jest, hooks) are injected by the runner, but importing from @jest/globals gives you proper types and is required under ESM.

import { describe, test, it, expect, jest, beforeAll, afterAll, beforeEach, afterEach } from "@jest/globals";Explicit imports — required for TS/ESM, optional otherwise.
import "@testing-library/jest-dom";Adds matchers like toBeInTheDocument().
import { render, screen, fireEvent, waitFor } from "@testing-library/react";React component testing.
import userEvent from "@testing-library/user-event";Realistic user interactions (click, type, tab).
import { rest, http, HttpResponse } from "msw";HTTP mocking — preferred over jest.mock("axios") for fetch-shaped APIs.

describe · test · itWriting tests

test("name", () => { expect(x).toBe(1); })Basic test. it is an alias.
describe("group", () => { test(...); })Group tests. Nest for shared beforeEach.
test.each([[1,2,3],[2,3,5]])("sum %i+%i=%i", (a,b,c) => ...)Parameterized — one row, one test.
test.only / describe.onlyRun just this. Forget to remove and CI passes vacuously.
test.skip / test.todo("write me")Skip or scaffold without failing.
test.concurrent("x", async () => ...)Run async tests in parallel within a file.
test("slow", () => ..., 10_000)Per-test timeout in ms (default 5000).
expect.assertions(2)Guard that n assertions ran — catches missing await in async tests.

before / after hooksLifecycle hooks

beforeAll(() => ...)Once before all tests in the file/describe.
afterAll(() => ...)Once after. Use for DB / server teardown.
beforeEach(() => ...)Before each test. Reset state here, not in beforeAll.
afterEach(() => jest.clearAllMocks())Standard cleanup. Or set clearMocks: true in config.
return Promise / async () => ...Hooks support async — await before tests start.
Hooks declared inside a describe only apply to tests in that block. Outer hooks still run.

expect(...) assertionsMatchers

Equality

.toBe(value)Strict Object.is — primitives + reference identity.
.toEqual(obj)Deep equality. Ignores undefined props.
.toStrictEqual(obj)Same, but rejects extra undefined + checks class.
.toBeCloseTo(0.3, 5)Float compare to n decimal digits.
.toMatchObject({ a: 1 })Partial match — extra keys allowed.

Truthiness, numbers, strings, arrays

.toBeTruthy() / .toBeFalsy() / .toBeNull() / .toBeUndefined() / .toBeDefined()Plain checks.
.toBeGreaterThan(n) / .toBeLessThanOrEqual(n)Numeric ordering.
.toBeNaN() / .toBeFinite()Number edge cases.
.toMatch(/regex/) / .toMatch("substr")String match.
.toContain(item) / .toContainEqual(obj)Array/string containment. Equal for deep.
.toHaveLength(n)Array/string length.
.toHaveProperty("a.b", value)Deep path check.

Async & errors

await expect(p).resolves.toBe(1)Await a promise that should resolve.
await expect(p).rejects.toThrow(/oops/)Await a promise that should reject.
expect(() => fn()).toThrow(TypeError)Sync throws — wrap in arrow.

Asymmetric & modifiers

expect.any(String) / expect.anything()Type / not-null placeholder in toEqual.
expect.objectContaining({ id: 1 })Partial inside larger object.
expect.arrayContaining([1, 2])Subset of array.
expect.stringMatching(/x/)Regex inside a struct.
expect(x).not.toBe(y)Negate any matcher.

fn · spy · moduleMocks & spies

const fn = jest.fn()Empty mock — returns undefined.
jest.fn((x) => x + 1)Mock with default implementation.
fn.mockReturnValue(42) / mockReturnValueOnce(42)Stage return values.
fn.mockResolvedValue(x) / mockRejectedValue(err)Async sugar.
fn.mockImplementation((x) => ...) / mockImplementationOnce(...)Swap behavior per-call.
jest.spyOn(obj, "method")Wrap a real method. Keeps original unless .mockImplementation is added.
spy.mockRestore()Restore original. Only works on spies, not jest.fn().
fn.mock.calls / .results / .instances / .contextsInspect every call after the fact.
expect(fn).toHaveBeenCalledTimes(n)Call count.
expect(fn).toHaveBeenCalledWith(arg1, expect.any(Number))Argument assertion.
expect(fn).toHaveBeenNthCalledWith(2, "x")Pin a specific call.
expect(fn).toHaveBeenLastCalledWith(...)Match only the last call.

Module mocking

jest.mock("./db")Auto-mock all exports. Hoisted above imports.
jest.mock("./db", () => ({ getUser: jest.fn() }))Factory mock.
jest.mock("axios", () => ({ __esModule: true, default: { get: jest.fn() } }))ES-default-export needs __esModule.
jest.unmock("./db")Opt this file out of an auto-mock.
jest.requireActual("./db")Get the real module inside a factory (partial mocks).
jest.doMock(path, factory)Edge Non-hoisted — use when factory needs locals.
__mocks__/.tsManual mock alongside node_modules or your module. Picked up by jest.mock("name").
javascript
import { jest } from "@jest/globals";
import * as db from "./db";

// 1. Plain mock function
const cb = jest.fn((x: number) => x * 2);
cb(3);
expect(cb).toHaveBeenCalledWith(3);
expect(cb).toHaveReturnedWith(6);

// 2. Stage return values per-call
const fetchUser = jest
  .fn()
  .mockResolvedValueOnce({ id: 1 })   // first call
  .mockResolvedValueOnce({ id: 2 })   // second
  .mockRejectedValue(new Error("nope")); // rest

// 3. Spy on a real method (keeps original by default)
const spy = jest.spyOn(console, "warn").mockImplementation(() => {});
// ...code under test...
spy.mockRestore();                     // undo

// 4. Module mock — must be top-level, hoisted above imports
jest.mock("./db", () => ({
  getUser: jest.fn(async (id: number) => ({ id, name: "Mock" })),
}));

// 5. Reset between tests (or set globally via jest.config)
afterEach(() => {
  jest.clearAllMocks();                // wipe call history
  // jest.resetAllMocks();              // also remove implementations
  // jest.restoreAllMocks();            // restore spies to originals
});
jest.mock() is hoisted. Babel moves the call above your imports — so the factory cannot reference module-scope variables. Use jest.doMock() if you need locals.

promises · fake timersAsync & timers

test("x", async () => { await ... })Preferred for promises.
return promise / await expect(p).resolves...Return or await — otherwise test passes too early.
test("x", (done) => { ...; done(); })Callback-style. Pass done(err) on failure.
jest.useFakeTimers()Replace timers + Date. Modern by default in Jest 27+.
jest.advanceTimersByTime(ms)Fast-forward timers.
jest.runAllTimers() / runOnlyPendingTimers()Flush all / only currently scheduled.
jest.setSystemTime(new Date("2026-01-01"))Pin Date.now() while fake timers are on.
jest.useRealTimers()Restore. Put in afterEach.
javascript
import { jest } from "@jest/globals";

// Async/await — return or await the promise
test("resolves user", async () => {
  await expect(getUser(1)).resolves.toEqual({ id: 1 });
  await expect(getUser(-1)).rejects.toThrow("not found");
});

// done() callback — only when you can't return a promise
test("emits done", (done) => {
  emitter.once("ready", (v) => {
    try { expect(v).toBe(42); done(); } catch (e) { done(e); }
  });
});

// Fake timers — control setTimeout / setInterval / Date
jest.useFakeTimers();
test("debounce fires after 250ms", () => {
  const fn = jest.fn();
  const d = debounce(fn, 250);
  d(); d(); d();
  jest.advanceTimersByTime(249);
  expect(fn).not.toHaveBeenCalled();
  jest.advanceTimersByTime(1);
  expect(fn).toHaveBeenCalledTimes(1);
});
// Flush all pending timers + microtasks
// await jest.runAllTimersAsync();

afterAll(() => jest.useRealTimers());

freeze output, diff over timeSnapshots

expect(value).toMatchSnapshot()Write to __snapshots__/file.snap. Compare on rerun.
expect(html).toMatchInlineSnapshot()Preferred — snapshot lives next to the test.
expect(obj).toMatchSnapshot({ id: expect.any(Number) })Property matcher — ignore volatile fields.
jest -u / jest --updateSnapshotAccept the current output as the new truth.
jest --ciFail rather than create missing snapshots.
Snapshots are tests, not artefacts. If reviewing a PR feels like just rubber-stamping the .snap diff, the snapshot is too big — assert on a smaller slice instead.

istanbul · thresholdsCoverage

jest --coveragePrint summary + write coverage/.
collectCoverageFrom: ["src/**/*.{ts,tsx}", "!**/*.d.ts"]Include files even when not imported by a test.
coverageThreshold: { global: { lines: 80, statements: 80, branches: 70, functions: 80 } }Fail CI below the bar.
coverageReporters: ["text", "lcov", "html", "json-summary"]Pick formats. lcov for Codecov etc.
coverageProvider: "v8"Faster Native Node coverage. "babel" is the older default.
/* istanbul ignore next */Skip a line/branch. Sparingly.

jest.config.* keysConfig

testEnvironment: "node" | "jsdom"DOM tests need jsdom.
preset: "ts-jest"TypeScript support without Babel.
transform: { "^.+\\.tsx?$": ["ts-jest", { isolatedModules: true }] }Explicit transform — isolatedModules speeds CI.
moduleNameMapper: { "^@/(.*)$": "<rootDir>/src/$1" }Path aliases — mirror your tsconfig paths.
setupFiles / setupFilesAfterEachBoot before runner / before each test (use for jest-dom).
testPathIgnorePatterns / testMatchWhere to find tests.
clearMocks: true / resetMocks: true / restoreMocks: trueAuto-cleanup between tests. Pick one — restoreMocks is the strictest.
maxWorkers: "50%"Throttle parallelism on CI.
projects: [...]Run multiple configs (node + jsdom) in one invocation.

flags you actually useCLI flags

--watch / --watchAllRerun on change. --watch needs git.
-t "name regex"Filter by test name.
--testPathPattern src/authFilter by file path.
--runInBand / -iSerial — for debuggers and shared state.
--bail / --bail=3Stop after first / Nth failure.
--silentSuppress console output from tests.
--detectOpenHandlesPrint what kept Node alive after tests — debug hung CI.
--logHeapUsageSpot memory leaks across tests.
--listTests / --showConfigInspect what Jest sees without running.

A full unit-test fileEnd-to-end · Cart checkout

Source + spec for a small cart module. Shows describe, sync + async tests, mocked dependency, and an assertion on call args.

javascript
// src/cart.ts
export const total = (items: { price: number; qty: number }[]) =>
  items.reduce((s, i) => s + i.price * i.qty, 0);

export async function checkout(items, pay) {     // pay: (cents) => Promise<{ok:boolean}>
  if (!items.length) throw new Error("empty cart");
  const cents = Math.round(total(items) * 100);
  const res = await pay(cents);
  if (!res.ok) throw new Error("payment failed");
  return { paid: cents };
}

// src/cart.test.ts
import { jest } from "@jest/globals";
import { total, checkout } from "./cart";

describe("cart", () => {
  test("total sums price*qty", () => {
    expect(total([{ price: 2, qty: 3 }, { price: 1.5, qty: 2 }])).toBe(9);
  });

  test("checkout calls pay with cents and returns receipt", async () => {
    const pay = jest.fn().mockResolvedValue({ ok: true });
    await expect(checkout([{ price: 9.99, qty: 1 }], pay)).resolves.toEqual({ paid: 999 });
    expect(pay).toHaveBeenCalledWith(999);
  });

  test("checkout rejects empty cart", async () => {
    await expect(checkout([], jest.fn())).rejects.toThrow("empty cart");
  });
});

Best practiceGood to know

Set restoreMocks: true in config. Spies auto-restore, mocks reset between tests — you forget once and the bug is silent state leak across files.
Prefer toStrictEqual over toEqual. It catches extra undefined keys and class mismatches. Saves you the "looked right, was wrong" hour.
Reach for MSW before jest.mock("axios"). Mock at the network layer, not the client — same setup runs in node + browser tests, survives swapping fetch/axios.

Common trapsWatch out for

Forgetting await in async tests. The test passes regardless of the assertion. Guard with expect.assertions(n) or always return the promise.
jest.mock() hoisting bites with TypeScript. The mock factory cannot close over module-scope consts — they’re not initialized yet. Either inline the mock or use jest.doMock.
Fake timers don’t flush microtasks. A setTimeout callback that awaits needs await jest.advanceTimersByTimeAsync(...) or an extra await Promise.resolve().

Go deeperSee also

Jest FAQ

What is Jest and what can you test with it?

Jest is a JavaScript testing framework made by Meta. It runs in Node.js and supports unit tests, integration tests, and snapshot tests for JavaScript, TypeScript, React, Vue, and Node.js code. It includes a test runner, assertion library, mocking system, and coverage reporter in one package.

What is the difference between jest.fn() and jest.spyOn()?

jest.fn() creates a standalone mock function with no implementation. jest.spyOn(obj, "method") wraps an existing method on an object so you can observe calls while keeping the original implementation (unless you chain .mockImplementation()). Use spyOn when you want to watch a real function; use fn() for injected dependencies.

How do I test asynchronous code in Jest?

Mark the test function as async and await the result, or return the promise. For callbacks, accept the done parameter and call done() when finished. Jest also supports resolves/rejects matchers: await expect(fetch(url)).resolves.toMatchObject({ status: 200 }).

What are Jest snapshots and when should I use them?

Snapshots serialize a value to a file on first run and diff against it on subsequent runs. Use them for UI component output (with React Testing Library or Enzyme) or complex serializable data structures. Avoid snapshotting large objects or frequently changing output — update with jest -u when the change is intentional.

How do I mock a module in Jest?

Call jest.mock("module-name") at the top of the test file to auto-mock all exports. Provide a factory function jest.mock("./api", () => ({ fetchUser: jest.fn() })) for manual control. For ESM modules, use jest.unstable_mockModule() or enable Babel transforms. Reset with jest.resetModules() between tests if needed.

How do I measure code coverage with Jest?

Run jest --coverage to generate an HTML report in coverage/lcov-report/index.html plus a console summary. Configure thresholds in jest.config.js under coverageThreshold to fail the build if coverage drops below a set percentage. Use collectCoverageFrom to control which files are measured.