How it works
The explanation, with examples you can run.
Three doubles in one test
The sign-up service below takes its dependencies as parameters. The test passes in a fake user store, a stub clock and a spy mailer, then checks the outcome. No database, no real time, no email.
Press Run to see what your code prints, in order.
Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.
The fake is what makes the second call meaningful: because it really stores the first user, the "taken" path is tested without a hand-written stub for each case.
With a framework
Vitest and Jest come with spies, mocks and fake timers built in. The same ideas, less code:
import { expect, test, vi } from "vitest"import { fakeUsers, makeSignup } from "./signup" // the functions from the example above test("sends one welcome email", async () => { vi.useFakeTimers() vi.setSystemTime(new Date("2026-01-01T00:00:00Z")) const send = vi.fn().mockResolvedValue(undefined) await makeSignup({ users: fakeUsers(), mailer: { send }, clock: { now: () => new Date().toISOString() } })("ada@example.com") expect(send).toHaveBeenCalledTimes(1) expect(send).toHaveBeenCalledWith("ada@example.com", "Welcome!") vi.useRealTimers()})When doubles hurt
- Over-mocking: if a test replaces every collaborator, it tests only that the code calls things in a certain order. Refactor, and it fails. Introduce a real bug in a collaborator, and it still passes.
- Mocking what you don't own: hand-written stubs of a third-party SDK capture what you think it does. Wrap the SDK in a small adapter of your own, double the adapter in unit tests, and test the adapter itself against the real thing (or a container) in an integration test.
- Stale stubs of other services: every stubbed HTTP response is a claim about another team's API. Contract tests are how you keep those claims true.
A useful rule: double things that are slow, non-deterministic or outside your control. Keep plain logic and value objects real.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.