Engineering Notes
Home/Testing/Testing/Jest
Testing · Testing

Jest

Jest bundles a runner, assertions, mocking, fake timers, snapshots and coverage. Learn the matchers and async patterns that avoid false passes, how jest.mock hoisting works, and when snapshots help or hurt.

🧪 Testing● Beginner⏱ 4 min read▶ 1 interactive🛠️ 3 exercises🎤 5 interview Q&As

🎯 What you'll learn

  • What Jest gives you and how tests are structured
  • Matchers and async tests
  • Mocks, spies, fake timers and snapshots
  • Configuration, workers and module hoisting

📚 Before you start

1What Jest Gives You

Jest is an all-in-one JavaScript/TypeScript test framework: a test runner (finds files, runs them in parallel workers), an assertion library (expect), mocking (functions, modules, timers), snapshot testing and coverage (via Istanbul/V8). Vitest offers a nearly identical API on top of Vite.

bash
npm i -D jest ts-jest @types/jest
npx ts-jest config:init          # or babel-jest / @swc/jest for speed
npx jest                         # all tests
npx jest cart -t "applies discount" --watch
npx jest --coverage

2Structure

ts
describe("Cart", () => {
  let cart: Cart;
  beforeEach(() => { cart = new Cart(); });          // fresh state per test (06-test_isolation_and_coverage/)

  test("starts empty", () => {
    expect(cart.total()).toBe(0);
  });

  describe("with items", () => {
    beforeEach(() => cart.add({ sku: "A", price: 10, qty: 2 }));
    it("sums line totals", () => expect(cart.total()).toBe(20));
  });

  test.each([
    [0, 0], [99, 0], [100, 10], [250, 25],
  ])("discount for subtotal %i is %i", (subtotal, discount) => {
    expect(discountFor(subtotal)).toBe(discount);
  });

  test.todo("handles currency rounding");
});

Hooks run in order: beforeAll (once per describe) → beforeEach (outer then inner) → test → afterEach (inner then outer) → afterAll.

3Matchers

MatcherUse
toBeObject.is equality (primitives, same reference)
toEqual / toStrictEqualdeep equality (Strict also checks undefined props and class types)
toMatchObjectsubset of an object
toContain, toHaveLength, toHavePropertycollections
toBeCloseTo(0.3, 5)floats
toThrow(/msg/)sync errors (wrap in a function!)
resolves / rejectspromises: await expect(p).rejects.toThrow("409")
expect.any(Date), expect.stringMatching(/^ord_/), expect.objectContainingasymmetric matchers inside structures

4Async Tests

ts
test("fetches user", async () => {
  const user = await api.getUser("u1");
  expect(user.name).toBe("Ana");
});

test("rejects unknown user", async () => {
  await expect(api.getUser("nope")).rejects.toThrow("not found");   // the await is essential
});

A test that forgets to await (or return) a promise finishes before the assertion runs — and passes. expect.assertions(n) guards against that.

5Mocks, Spies and Fakes

ts
const send = jest.fn().mockResolvedValue({ id: "m1" });         // mock function
expect(send).toHaveBeenCalledWith("a@x.io", expect.stringContaining("Welcome"));
expect(send).toHaveBeenCalledTimes(1);

const spy = jest.spyOn(Math, "random").mockReturnValue(0.42);  // spy on an existing method
spy.mockRestore();

jest.mock("./mailer", () => ({ sendMail: jest.fn() }));         // module mock (hoisted above imports)
import { sendMail } from "./mailer";

jest.useFakeTimers();                                           // control setTimeout / Date
jest.advanceTimersByTime(60_000);
jest.useRealTimers();

Config to keep tests independent: clearMocks/restoreMocks: true in jest.config. Prefer passing dependencies in (05-test_doubles/) over module mocking where you can.

6Snapshots

expect(render(<Invoice />)).toMatchSnapshot() stores output in __snapshots__ and compares on later runs. Useful for serialized output that's tedious to assert; dangerous when big snapshots get updated blindly (-u). Prefer small inline snapshots (toMatchInlineSnapshot()) and explicit assertions for important values.

7Configuration

js
// jest.config.js
module.exports = {
  preset: "ts-jest",
  testEnvironment: "node",                // "jsdom" for DOM tests
  roots: ["<rootDir>/src"],
  setupFilesAfterEnv: ["<rootDir>/test/setup.ts"],
  restoreMocks: true,
  collectCoverageFrom: ["src/**/*.ts", "!src/**/*.d.ts"],
  coverageThreshold: { global: { branches: 80 } },
};
Under the HoodWorkers, Module Registry and Hoisting▾

Jest runs test files in parallel in a pool of worker processes (--maxWorkers); tests within a file run serially. Each test file gets its own module registry and its own vm context (sandboxed globals), so a module imported in two files is evaluated twice — that's how files are isolated from each other, and also why Jest is slower than plain Node at startup. Jest replaces Node's require with its own resolver, which is what makes jest.mock() possible: before any module is loaded, Babel/ts-jest hoists jest.mock(...) calls to the top of the file, and the custom require returns the mock from the registry instead of the real module. (This is also why variables referenced inside a jest.mock factory must be prefixed mock… — the factory runs before normal top-level code.) Fake timers replace setTimeout, Date and friends in that file's context with a controllable clock (@sinonjs/fake-timers under the hood).

Jest discovers test files and runs each in its own worker sandbox with a separate module registry; hoisted jest.mock calls swap modules before import, hooks wrap each test, and results and coverage are aggregated
Jest discovers test files and runs each in its own worker sandbox with a separate module registry; hoisted jest.mock calls swap modules before import, hooks wrap each test, and results and coverage are aggregated

⚠️ Common Pitfalls

  1. Not awaiting promises in tests — false passes.
  2. Shared state across tests — order-dependent failures.
  3. Over-mocking — tests that only check the mocks.
  4. Blind snapshot updates.
  5. toBe on objects — compares references; use toEqual.
  6. Leaked fake timers / spies — enable restoreMocks and restore timers.

📋 Quick Reference

text
describe / test / it / test.each / test.todo · beforeAll/beforeEach/afterEach/afterAll
expect(x).toBe / toEqual / toMatchObject / toThrow ; await expect(p).resolves|rejects
jest.fn / spyOn / mock(module) · useFakeTimers / advanceTimersByTime
npx jest -t "name" --watch --coverage

🧠 Key takeaways

  • describe/test/expect + rich matchers (toEqual, toThrow, toHaveBeenCalledWith, resolves/rejects).
  • Await or return promises in async tests, or failures slip through.
  • jest.mock() is hoisted; jest.fn/spyOn for doubles; fake timers for time-based code.
  • Jest runs files in parallel workers with isolated module registries; use snapshots sparingly. (Vitest offers a compatible API.)

🎤 Interview Q&A

Questions interviewers ask about this topic. Answer out loud first, then open a card to compare.

Q1How do you mock modules and functions in Jest?core▾

jest.fn() for standalone mocks, jest.spyOn(obj, 'method') to spy/replace an existing method, jest.mock('./module') to auto-mock a module (hoisted above imports), with mockResolvedValue etc. for async functions. Restore with mockRestore/restoreMocks config.

Follow-ups interviewers ask
  • Why is jest.mock hoisted? So the mock is in place before the module under test imports it.
Q2How do you test asynchronous code in Jest?core▾

Return a promise or use async/await in the test and await expect(fn()).resolves/rejects. For timers, use fake timers (jest.useFakeTimers(), jest.advanceTimersByTime).

Follow-ups interviewers ask
  • Common mistake? Forgetting to await, so the test finishes before assertions run.
Q3What are snapshot tests and when are they useful?core▾

They serialize output (component trees, objects) and compare with a stored snapshot. Useful for detecting unintended changes in large outputs; harmful when snapshots are huge and people update them blindly.

Follow-ups interviewers ask
  • Better alternative for components? Testing Library assertions on visible behaviour.
Q4How do you structure setup and teardown in Jest?senior▾

beforeAll/afterAll for expensive shared setup, beforeEach/afterEach for per-test state, scoped by describe blocks. Keep tests independent; avoid sharing mutable state.

Follow-ups interviewers ask
  • Jest vs Vitest? Vitest is Jest-compatible, faster with Vite and native ESM.
Q5How do you test React components with Jest?core▾

With React Testing Library: render the component, query elements by role/label/text as users would, simulate events with user-event, and assert on the DOM. Mock network calls with MSW.

Follow-ups interviewers ask
  • Why query by role? It reflects accessibility and user-visible behaviour, not implementation.

🛠️ Practice exercises

Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.

✅ Check yourself

Answer the questions — feedback is instant.

1. toBe vs toEqual for objects?
Object.is vs recursive.
2. A test with an un-awaited promise assertion…
Return or await it.
3. Why does jest.mock work even though it's written after imports?
Hoisting.
4. Testing a 5-second debounce quickly:
Control time.