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.
🎯 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.
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 --coverage2Structure
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
| Matcher | Use |
|---|---|
toBe | Object.is equality (primitives, same reference) |
toEqual / toStrictEqual | deep equality (Strict also checks undefined props and class types) |
toMatchObject | subset of an object |
toContain, toHaveLength, toHaveProperty | collections |
toBeCloseTo(0.3, 5) | floats |
toThrow(/msg/) | sync errors (wrap in a function!) |
resolves / rejects | promises: await expect(p).rejects.toThrow("409") |
expect.any(Date), expect.stringMatching(/^ord_/), expect.objectContaining | asymmetric matchers inside structures |
4Async Tests
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
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
// 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).
⚠️ Common Pitfalls
- Not awaiting promises in tests — false passes.
- Shared state across tests — order-dependent failures.
- Over-mocking — tests that only check the mocks.
- Blind snapshot updates.
toBeon objects — compares references; usetoEqual.- Leaked fake timers / spies — enable
restoreMocksand restore timers.
📋 Quick Reference
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/spyOnfor 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
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.
- 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).
- 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.
- 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.
- 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.
- 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.