Free — no signup required

The Danger of Over-Mocking

2 min read

Testing Implementation Details

One of the most common and damaging pitfalls in unit testing is mocking too aggressively. If you mock every internal function call, you are no longer testing behavior — you are encoding your current implementation into the test. The test becomes a mirror of the code rather than a specification of what the code should do.

The Danger:
Suppose you refactor calculateTotal() to use a built-in sum() function instead of a loop that calls add(a, b) repeatedly. The behavior is identical — the output is the same. But if your test mocked add and asserted it was called N times, the test now fails. You have a passing refactor that breaks tests. This is called a brittle test, and it is worse than no test at all because it erodes trust in the test suite.

The Rule:
Mock only at the boundaries of your system — the edges where your code talks to the outside world:
- Network calls (HTTP APIs, gRPC services)
- Database I/O
- Filesystem reads and writes
- Message queues
- System clock (datetime.now(), time.Now())
- Random number generators

Do not mock your own internal logic classes, helper functions, or pure computation. If you feel the urge to mock an internal function, it is usually a signal that the function should be extracted into a separate, independently testable unit.

Interview Tip

A common senior-level interview question is: "What makes a test brittle?" The answer centers on coupling tests to implementation details rather than behavior. Mention over-mocking as the primary cause, and contrast it with testing at the boundary. A strong follow-up point: "Tests should be refactoring-safe — if I change how the code works without changing what it does, no tests should break."

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free