Mocking & Stubbing
Test doubles stand in for a unit's real dependencies so you can test it in isolation — feeding it controlled inputs and observing its interactions without hitting a network, database, or clock. Used well, they make tests fast, deterministic, and focused. Used poorly, they make tests brittle and meaningless.
The single most important rule: mock at boundaries — external APIs, databases, time, randomness — and let your own logic run for real. When tests mock internal implementation, they stop verifying behavior and start verifying that you called your own methods, which breaks on every refactor and proves nothing.
TL;DR
- Use test doubles to isolate units.
- Stub return values; mock interactions.
- Don't over-mock — prefer real implementations when simple.
- Mock at boundaries (APIs, databases, time), not internals.
Quick Example
A stub feeds canned data; the real logic runs against it:
Types of Test Doubles
When to mock
- External APIs and services
- Databases (for unit tests)
- Time-dependent code
- Random/non-deterministic code
- Slow operations
When NOT to mock
- Simple value objects and pure functions
- The code you're actually testing
- Everything — over-mocking yields brittle, meaningless tests
Mocking in Practice
JavaScript (Jest)
Python (unittest.mock)
Mocking Time
Time is the classic non-deterministic dependency — freeze it:
Common Patterns
Partial mocking — fake one method, run the rest
Fake — a working, simplified implementation
Best Practices
- Mock at boundaries (APIs, DB, clock, randomness); let your logic run.
- Stub for inputs, mock for interactions — pick the double to the intent.
- Prefer fakes over heavy mocks for stateful dependencies like repositories.
- Reset/restore doubles between tests to avoid leakage.
- If you're mocking a lot, treat it as a design smell — the unit may have too many dependencies.
Common Mistakes
Mocking internal implementation
Over-mocking until the test proves nothing
FAQ
What's the difference between a mock and a stub?
A stub provides canned return values — it controls what the unit receives. A mock additionally asserts that specific interactions happened — it verifies what the unit does. Use a stub when you just need to feed data in; use a mock when the interaction itself (e.g. "an email was sent") is the behavior under test. Conflating the two leads to tests that over-specify implementation.
Why is "mock at boundaries" the golden rule?
Because mocking your own internals makes tests verify how code works rather than what it does — so any refactor breaks them even when behavior is unchanged, and the tests can pass while the real logic is broken. Mocking only the boundaries (network, DB, time) keeps your actual logic under test while removing the slow, flaky, non-deterministic parts.
When should I use a fake instead of a mock?
Use a fake (a working, simplified implementation — like an in-memory repository) when a dependency is stateful and used across many tests. Fakes let real interactions happen against simple storage, producing tests that read naturally and don't break on refactors. Reach for mocks when you specifically need to verify an interaction occurred; use fakes when you need believable behavior.
How do I test code that depends on the current time or randomness?
Make those dependencies controllable. For time, freeze the clock (jest.setSystemTime, freezegun) or inject a clock object. For randomness, inject a seeded or fixed source. This removes non-determinism so the test produces the same result every run — and it's the same dependency-injection design that makes code testable in the first place. See Unit Testing.
Related Topics
- Unit Testing — Testing fundamentals
- TDD — Where injectable dependencies come from
- Integration Testing — When to use real dependencies
- Jest · Pytest — Built-in mocking
- Vitest — Vite-native mocking