New: Debug encrypted microservice traffic with Speedscale's eBPF collector Read the announcement

A cardboard cutout of a delivery truck on a film set stands in for the real truck while a developer at a laptop points a remote at it

What Mocks Are Actually For

Part 1 of 9 in the Getting Started with Mocks series.


Mocks can make tests more reliable by giving the test control over a dependency. Yet most tutorials begin with a library: install it, learn its decorators or expectation syntax, and start replacing things. That skips the decision that matters: what should you replace, and what will the resulting test prove?

This nine-part series is for developers who can write a basic test but get stuck when the code calls an API, a database, a clock, or a queue. Every post shows the same example in Java, Node.js, Go, and Python, with runnable code in an open source companion repository.

🎯 Key Takeaways
  • A mock gives a test control over a dependency’s behavior.
  • You need a mock when a dependency is slow, unavailable, expensive, non-deterministic, or not built yet.
  • A mock buys control by giving up reality. Every mocked test proves something and skips something.

The test cannot control the carrier

Take a function that decides whether to warn a customer that their package is late. It asks a carrier’s API for the shipment status, and if the answer is delayed it produces a message.

Now test the delayed case. Call the function with a tracking number and expect the warning. Where does a delayed package come from?

The carrier owns that answer and the test cannot ask for it. You can hunt for a tracking number that is late today and watch the test break when the truck arrives. You can use a sandbox, but most sandboxes expose only happy paths. Few offer a “make it late” button.

Slow and flaky are the usual complaints about tests that touch real services. The deeper problem is control: the test cannot create the situation it exists to check.

The job of a mock

A mock, in the loose sense most developers use the word, is a stand-in for something your code depends on. Gerard Meszaros coined the general term “test double” for any pretend object used in place of a real one, borrowing the idea from stunt doubles in film, and Martin Fowler’s Mocks Aren’t Stubs from 2007 is where most of the vocabulary settled.

Strip away the taxonomy and the job is control. The test hands the code a version of the carrier that says delayed because the test said so. Now the package is late on demand, and the test can check the decision the code makes.

That is the whole idea. Everything else in this series is a variation on where you put the stand-in and what you let it do.

Swapping the carrier out like that is only possible if the code lets you. The place where you can change what code does without editing that code is called a seam, and cutting one is the subject of the next post. Without a seam there is nothing for a stand-in to replace, which is why so much of mocking turns out to be ordinary design work.

flowchart LR
    T[Test] --> N[Package notifier]
    N --> S{Shipment status}
    S -->|in production| R[Real carrier API]
    S -->|in the test| M["Stand-in: always says delayed"]

When you need one

You need control when a dependency is one of these:

  • Slow. A real network round-trip per test turns a two-second suite into a two-minute one, and people stop running two-minute suites.
  • Unavailable. The dependency needs credentials, a VPN, a running cluster, or a paid account that your CI runner does not have.
  • Expensive. Every call costs money, sends an email, or charges a card.
  • Non-deterministic. Clocks, random numbers, and anything that changes between two runs.
  • Not built yet. The team next door is still writing the API you are integrating with.

The first four are about running tests reliably. The last one is about parallel work: agree on the shape of the answer and both teams can move.

If you want a longer catalog of situations, this site has one in Ways to Use Mock Services in Software Development. This series is about judgment, not the catalog.

What a mock cannot do

A mocked test proves how your code behaves given an answer. It does not prove that the real dependency gives that answer.

In the package example, the mocked test proves the warning appears when the carrier says delayed. It does not prove the carrier is reachable, that the URL is right, that the response field is really called status, or that production points at the right host. All of that was replaced. That was the point.

This trade is the center of the series, so every post from here on ends with two short sections: what this test proves and what this test does not prove. Writing the second one down is the habit that separates useful mocking from wishful mocking.

The cost of forgetting the trade

Mocks are cheap to write, and that is how they get overused. Google’s testing blog said so in 2013 in Don’t Overuse Mocks: mocks make tests easy to write and easy to misuse, and when a real or in-memory version of a dependency is practical, it is often the better choice.

The book Software Engineering at Google is blunter about what happened at scale. Its chapter on test doubles describes mock-heavy tests that “required constant effort to maintain while rarely finding bugs,” and reports that “the pendulum at Google has now begun swinging in the other direction, with many engineers avoiding mocking frameworks in favor of writing more realistic tests.”

The failure mode is specific. A stand-in encodes your belief about the dependency at the moment you wrote it. The dependency changes, the belief does not, and the suite stays green over a client that has not worked in a month. Post 7 in this series tests the real wire, and post 8 is about catching the drift.

A word on the word “mock”

This series says “mock” the way most developers do: loosely, for any stand-in. That is deliberate. Fowler’s line is that “only mocks insist upon behavior verification,” while stubs hand back canned answers and let the test check state. The precise names, stub, spy, fake, mock, and dummy, arrive in post 6, after you have used three of them without needing the labels. If you want the taxonomy now, the site’s Mock vs Stub post covers it.

What the series covers

One example runs through all nine posts: a package notifier that asks a carrier for a status, decides whether to warn the customer, sends the notification, and records the result. It grows one collaborator at a time. Every code example is shown in Java, Node.js, Go, and Python, and the full runnable code lives in the companion repository.

  1. What Mocks Are Actually For. This post.
  2. Your First Useful Mock. Cut the seam, stub the carrier, and test delayed and delivered packages.
  3. Make Failure Boring with Mocks. Produce timeouts, server errors, and unexpected responses on demand.
  4. Did It Actually Send?. Use a spy to test side effects.
  5. Test Behavior, Not Choreography. Rewrite a brittle test that fails on a harmless refactor.
  6. They Aren’t All Mocks. Learn the vocabulary after using each kind of test double.
  7. Mock the Wire. Test the real client with a fake HTTP server.
  8. Your Mock Is Lying. Detect contract drift before production does.
  9. When Handwritten Mocks Stop Scaling. Choose the right level of fidelity as the system grows.

Next: the test you cannot write, one seam, and the smallest mock that makes a delayed package happen on purpose. Your First Useful Mock.

Series navigation: All nine posts · Next: Your First Useful Mock

Stop writing API mocks by hand

proxymock records real traffic from your running app and replays it as mocks — HTTP, gRPC, Postgres, Kafka, and more. Install in 30 seconds, no account required.