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.
Get the real module inside a factory (partial mocks).
jest.doMock(path, factory)
Edge Non-hoisted — use when factory needs locals.
__mocks__/.ts
Manual 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.
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.
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().
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.