API Mocking vs Stubbing vs Faking: The Developer's Definitive Guide
API mocking uses pre-programmed objects that verify call behavior during tests, while stubbing returns hard-coded responses without assertions. Mocks validate how an API is called; stubs provide canned data. Choose mocking when interaction patterns matter, and stubbing when you only need predictable return values.
Gary Bernhardt called it "the terminology swamp." Mock, stub, fake, spy, dummy — five words that most developers use interchangeably, but which describe five distinct test double patterns. Getting the terminology right isn't pedantic busywork; it directly affects which tool you reach for and whether your test suite is trustworthy.
The Taxonomy: Five Test Doubles
Gerard Meszaros coined these definitions in xUnit Patterns, and they remain the clearest framework:
- Dummy — passed to fill a parameter slot but never used.
nulloften qualifies. - Stub — returns hard-coded responses to specific inputs. Has no assertions.
- Spy — a stub that also records how it was called. You assert on the recording afterward.
- Mock — pre-programmed with expectations. Verifies call behaviour during the test, not after.
- Fake — a working implementation that takes a shortcut unsuitable for production (e.g., in-memory DB).
When to Stub an API
Stubs are the right tool when you need deterministic responses and don't care how many times the collaborator is called. Classic use case: your order service calls a payment gateway. In tests, you stub the gateway to return { status: "approved" } without hitting real infrastructure.
// Stub example — Vitest
vi.mock("./payment-gateway", () => ({
charge: vi.fn().mockResolvedValue({ status: "approved", id: "ch_123" })
}))
When to Mock an API
Use mocks when the interaction itself is what you're testing. Did your checkout function call charge() exactly once? With the right amount? Mocks answer these questions by failing the test if expectations aren't met.
// Mock with Jest expectations
const charge = jest.fn()
expect(charge).toHaveBeenCalledOnce()
expect(charge).toHaveBeenCalledWith({ amount: 2999, currency: "usd" })
When to Use a Fake API
Fakes shine in integration tests. An in-memory Express server that implements your payment API's contract is a fake — it behaves correctly, persists data during the test run, and tears down cleanly. Tools like moqapi.dev generate live fake APIs from your OpenAPI spec, making fakes practical without writing server code.
The Practical Decision Tree
- Testing a specific function call? → Mock
- Just need a response and don't care how? → Stub
- Need realistic end-to-end behaviour? → Fake
- Need to verify side effects were recorded? → Spy
Common Mistakes
Over-mocking — mocking every dependency creates brittle tests that break on every refactor, even when the feature still works. Limit mocks to external I/O (databases, HTTP calls, clocks).
Stub leakage — stubs that aren't reset between tests cause false positives. Use beforeEach teardown consistently.
Fake drift — a fake that doesn't keep up with the real API's contract gives false confidence. Keep fakes behind a contract test so they're always accurate.
Summary
The right test double makes your tests fast, readable, and trustworthy. The wrong one gives you a green suite and a production incident. Choose deliberately.
About the Author
Founder and sole developer of moqapi.dev. Full-stack engineer with deep experience in API platforms, serverless runtimes, and developer tooling. Built moqapi to solve the mock data and deployment friction she experienced firsthand building production APIs.
Related Articles
What Is Mock Data and Why It Matters for Modern Development
Understand mock data, its role in frontend and backend testing, and how moqapi.dev automates the creation of realistic test payloads for every API endpoint.
Building Serverless APIs: 10 Best Practices You Should Follow
From cold-start optimisation to function composition, learn battle-tested patterns for shipping production-grade serverless APIs at scale.
API Testing Strategies for Modern Engineering Teams
Contract tests, snapshot tests, fuzz testing — explore the testing matrix every team needs, with examples using Node.js, Python, and moqapi.dev.
Ready to build?
Start deploying serverless functions in under a minute.