Published on

|

10 min

Ayush Shrivastava
Ayush Shrivastava
Cover Image for Jest Framework: A Practical Guide (Mocks, spyOn, Coverage, CI) — 2026 Edition

Jest Framework: A Practical Guide (Mocks, spyOn, Coverage, CI) — 2026 Edition

The Jest framework is still the go-to testing setup for a ton of JavaScript and TypeScript teams in 2026 because it’s productive out of the box: a test runner, assertions, mocking, snapshots, and coverage in one place.

This Jest tutorial is written for busy engineers. You’ll learn:

  • the 80/20 of writing great tests (AAA pattern)

  • mocks that don’t rot

  • Jest spyOn (and the common “why is this not spying?!” traps)

  • coverage that’s not fake confidence

  • CI that stays stable


Table of contents

  1. What is the Jest framework?

  2. Install Jest (JS/TS) fast

  3. Jest tutorial: the AAA pattern that keeps tests readable

  4. Mocks in Jest: jest.fn vs jest.mock vs jest.spyOn

  5. Jest spyOn explained (real examples + pitfalls)

  6. Coverage in Jest (thresholds + providers)

  7. CI setup (GitHub Actions) + common flake fixes

  8. FAQ (targets “jest tutorial” + “jest spyon”)


Ebook Preview

Get the Mobile Testing Playbook Used by 800+ QA Teams

Discover 50+ battle-tested strategies to catch critical bugs before production and ship 5-star apps faster.

100% Free. No spam. Unsubscribe anytime.

1) What is the Jest framework?

Jest is a JavaScript testing framework commonly used for unit tests and component tests in JS/TS projects. It also provides mocking utilities and built-in coverage reporting.

If you’re modernizing your stack: Jest 30 requires at least Node 18.x (and has TypeScript minimum changes), so keep your CI/runtime aligned.


2) Install Jest (JS + TypeScript) in minutes

JavaScript (Node) setup

npm i -D jest

TypeScript setup (practical default)

npm i -D jest ts-jest @types/jest

Add scripts:

{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:ci": "jest --ci"
}
}

Basic config (jest.config.js):

/** @type {import('jest').Config} */
module.exports = {
testEnvironment: "node",
clearMocks: true,
testMatch: ["**/?(*.)+(spec|test).[jt]s?(x)"],
};

If you’re on Next.js, use their official Jest guide because config differs (App Router vs Pages Router, CSS modules, etc.).


3) Jest tutorial: write tests that don’t suck (AAA pattern)

Most Jest test suites become painful for one reason: tests are hard to read. Fix that with AAA:

  • Arrange: inputs + mocks

  • Act: execute the unit

  • Assert: verify outcome + side effects

Example:

import { calculateTotal } from "./billing";
test("adds tax to subtotal", () => {
// Arrange
const subtotal = 100;
const taxRate = 0.18;
// Act
const total = calculateTotal(subtotal, taxRate);
// Assert
expect(total).toBe(118);
});

Want a strict template with naming rules? Check aaa pattern of unit testing out.


4) Mocks in the Jest framework: jest.fn vs jest.mock vs jest.spyOn

Here’s the simplest mental model (and yes, this is what keeps suites maintainable):

jest.fn() — mock a function you control

Use when you own the function reference and just need call tracking + return values.

const sendEmail = jest.fn().mockResolvedValue({ ok: true });
await sendEmail("a@b.com");
expect(sendEmail).toHaveBeenCalledWith("a@b.com");

Jest’s docs call these “mock functions” and they can act as spies because they let you observe calls.

jest.mock() — mock an entire module (powerful, easy to overuse)

Use when you need to isolate external boundaries (payments, analytics SDKs, filesystem, etc.).

jest.mock("./payments", () => ({
chargeCard: jest.fn().mockResolvedValue({ id: "ch_123" }),
}));

Rule: mock boundaries, not internals. Module mocks are how teams accidentally create “tests that pass while prod breaks.”

jest.spyOn() — spy on a real method (best for “verify it was called”)

Use when you want to observe calls on an existing object method, and optionally override it.

And yes—people often type “jest spyon” in Google when they mean Jest spyOn. This section is for that exact search intent.


5) Jest spyOn explained (with real examples + common pitfalls)

Example A: spy without changing behavior

import { analytics } from "./analytics";
test("tracks purchase event", () => {
const spy = jest.spyOn(analytics, "trackEvent");
analytics.trackEvent("purchase", { price: 299 });
expect(spy).toHaveBeenCalledWith("purchase", { price: 299 });
});

Example B: spy + override behavior (common in unit tests)

const spy = jest
.spyOn(apiClient, "getUser")
.mockResolvedValue({ id: 7, name: "Asha" });
const result = await handler(7);
expect(spy).toHaveBeenCalledWith(7);
expect(result.name).toBe("Asha");

Pitfall #1 (most common): you spied on the wrong reference

If production code imports a function directly:

import { trackEvent } from "./analytics";

…and your test spies on an object:

jest.spyOn(analytics, "trackEvent");

…your spy might never see calls because the runtime uses the imported binding.

Fix: spy on the same reference the code uses (often by importing the whole module):

import * as analyticsModule from "./analytics";
test("tracks purchase", () => {
const spy = jest.spyOn(analyticsModule, "trackEvent");
analyticsModule.trackEvent("purchase", { price: 299 });
expect(spy).toHaveBeenCalled();
});

Pitfall #2: you forgot to restore spies (leaks state across tests)

This is the #1 reason suites become flaky over time.

Add:

afterEach(() => {
jest.restoreAllMocks();
});

Important detail: jest.restoreAllMocks() only reliably restores mocks created with jest.spyOn() (and some replaced properties).

Pitfall #3: using mockClear when you needed mockRestore

Quick cheat sheet:

  • mockClear() = clears call history

  • mockReset() = clears call history + resets implementation

  • mockRestore() = restores original implementation (spyOn only)


6) Coverage in Jest: don’t let the number lie to you

In the Jest framework, coverage is easy:

jest --coverage

But coverage can still mislead you if config is sloppy or CI uploads partial results.

Set thresholds (so CI fails when quality slips)

In jest.config.js:

module.exports = {
collectCoverageFrom: [
"src/**/*.{js,jsx,ts,tsx}",
"!src/**/*.d.ts",
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
};

Coverage provider: babel vs v8

Jest supports a coverageProvider option with allowed values babel (default) or v8.

Practical rule:

  • If sourcemaps/transforms are confusing your reports, try coverageProvider: "v8" in CI and validate on a few PRs.


7) CI setup: GitHub Actions that stays stable

A clean job:

name: test on: [push, pull_request]

jobs:
jest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test -- --ci --coverage

If you’re on Jest 30+, ensure Node is >= 18.x, otherwise CI will break in annoying ways.

Next.js note (real-world gotcha)

Next’s official docs call out current limitations around testing async Server Components with Jest, and often recommend E2E for those paths.


8) Best practices that keep Jest suites healthy

  1. Prefer testing behavior over implementation: If every test is spyOn + “called with X”, you’re testing wiring, not correctness.

  2. Mock boundaries, not your own core logic: Mock network, DB clients, analytics, payment SDKs—avoid mocking your domain functions unless you have a specific reason.

  3. Use AAA + consistent naming: Your future self will thank you. Link for AAA Pattern in Unit Testing

  4. Coverage is a map, not proof: A file can be “covered” with zero meaningful assertions.

  5. Pair unit/component tests with real execution for UX-critical flows: Unit tests won’t catch “checkout broke on a real device because UI timing changed.” That’s where device-level execution testing matters.


FAQ (targets “jest tutorial” + “jest spyon” search intent)

What is the Jest framework used for?

Unit tests and component tests in JS/TS projects, with built-in mocking utilities and coverage tooling.

What is “jest spyon”?

It’s a common misspelling of Jest spyOn (jest.spyOn()), used to observe calls to an existing method and optionally override it.

Does jest.restoreAllMocks() restore everything?

No. Jest warns it mainly works for mocks created with jest.spyOn() (and certain replaced properties).

Is Jest 30 safe to upgrade to?

Yes, but verify your Node version (>= 18.x) and follow upgrade notes, especially in CI.