In short
Your signup code sends an email and charges a card. You don't want tests spamming inboxes or charging real money. Mocks and stubs swap those parts for fakes you control.
Replacing real dependencies (a database call, an external API, the current time) with fake, controllable versions in your tests — so tests are fast, deterministic and isolated.

Think of it like this
The bowling machine in the nets
In the nets, a bowling machine sends down exactly the delivery you set, every single time. No tired bowler, no surprises. A stub is that machine: a fake dependency that returns exactly what the test needs. A mock goes one step further and also records every ball it bowled.
Watch it happen
Testing signup with a fake mailer
stub: resolve ok
1/5The test replaces the real email client with a fake that always succeeds.
The big ideas
What you need to know
Stub: canned answers
A stub returns fixed values, like 'payment succeeded', so you can test what your code does next.
Mock: records calls
A mock or spy also remembers how it was called, so you can assert 'sent exactly one email'.
Inject dependencies
Pass the mailer or payment client into your function. Then swapping in a fake is trivial.
Don't mock everything
Mock what you don't own, like email and payment APIs. Mocking your own queries can hide real bugs.
See it in code
Walk through the code
1import { jest, test, expect } from "@jest/globals";2import { signup } from "./signup";3 4test("sends a welcome email after signup", async () => {5 const mailer = { send: jest.fn(async () => ({ ok: true })) };6 const users = { create: jest.fn(async (email: string) => ({ id: 1, email })) };7 8 await signup("riya@test.dev", { mailer, users });9 10 expect(users.create).toHaveBeenCalledWith("riya@test.dev");11 expect(mailer.send).toHaveBeenCalledTimes(1);12 expect(mailer.send).toHaveBeenCalledWith(expect.objectContaining({ to: "riya@test.dev" }));13});1/3Fakes for the mailer and user store. jest.fn records every call and returns what you tell it to.
This works because signup takes its dependencies as an argument. For imports you can't inject, jest.mock() can replace a whole module.
Save yourself the pain
Common mistakes
Oops
Mocking so much that the test only checks your mocks.
Do this instead
If every collaborator is fake, the test proves little. Mock the edges and keep your real logic real.
Oops
Leaving mocks in place between tests.
Do this instead
Call jest.restoreAllMocks() or turn on restoreMocks in config, so one test's fakes don't leak into the next.
Your mission
Fake a payment gateway
Quick check
You want your test to check that sendEmail was called exactly once. What do you need?
Go deeper
- 1Mock a third-party API call in a test so it doesn't make a real network request.
- 2Stub a database call to return known test data without hitting a real database.
- 3Learn the difference between a mock (verifies it was called) and a stub (just returns data).
More topics at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.