Skip to main content
Mocking and Stubbing

Mocking and Stubbing: Building Test Doubles with Real-World Analogies

{ "title": "Mocking and Stubbing: Building Test Doubles with Real-World Analogies", "excerpt": "Confused about mocks and stubs? This guide uses everyday analogies—like a restaurant kitchen, a warehouse logistics team, and a smart home system—to demystify test doubles. We explain what mocks, stubs, fakes, and spies are, why they matter, and how to use them effectively without overcomplicating your tests. Learn when to stub a dependency versus mock a collaboration, see common pitfalls like overspe

{ "title": "Mocking and Stubbing: Building Test Doubles with Real-World Analogies", "excerpt": "Confused about mocks and stubs? This guide uses everyday analogies—like a restaurant kitchen, a warehouse logistics team, and a smart home system—to demystify test doubles. We explain what mocks, stubs, fakes, and spies are, why they matter, and how to use them effectively without overcomplicating your tests. Learn when to stub a dependency versus mock a collaboration, see common pitfalls like overspecification, and get a step-by-step process for choosing the right double. Written for beginners and teams looking to improve test reliability, this article provides concrete, actionable advice. Published April 2026.", "content": "

Introduction: Why Test Doubles Matter

When you start writing automated tests, you quickly hit a wall: your code depends on other code—databases, APIs, file systems, or external services. Testing these interactions directly is slow, brittle, and often impossible in isolation. That's where test doubles come in. They are stand-ins for real dependencies, allowing you to control the environment and verify specific behaviors without setting up a full system. This guide, published April 2026, focuses on two of the most commonly confused doubles: mocks and stubs. Using real-world analogies, we'll clarify what they are, how they differ, and—most importantly—when to use each. By the end, you'll have a mental framework that makes choosing between a mock and a stub feel natural, not academic.

Core Concepts: What Are Test Doubles?

Test doubles are objects that replace real components in your system during testing. The term comes from the film industry, where a stunt double stands in for an actor. Similarly, a test double stands in for a production dependency. There are several types: dummies (passed around but never used), fakes (working but simplified implementations, like an in-memory database), stubs (provide canned answers), mocks (expect and verify interactions), and spies (record calls for later verification). Understanding these distinctions helps you write tests that are reliable, fast, and focused on the behavior you care about.

Stubs: The Understudy Who Knows Their Lines

Imagine a theater understudy who must deliver a specific line on cue. In testing, a stub is an object that returns predefined data when called. For example, if your code calls a weather API, a stub might always return \"sunny\" without hitting the real server. You use stubs when you need to control what a dependency returns—so you can test how your code handles different responses. Stubs are simple: they don't care how many times they're called or in what order; they just provide the canned answer.

Mocks: The Director Checking Every Move

Now picture a film director who watches every action of an actor, expecting precise movements. A mock is more active: it not only provides canned answers but also expects specific interactions—methods called with certain arguments, in a particular order. If the code calls a method incorrectly, the mock fails the test. Mocks are used when you want to verify that your code communicates with dependencies correctly. For instance, you might mock a payment gateway to ensure that the 'charge' method is called with the right amount.

Fakes, Spies, and Dummies: The Supporting Cast

Fakes are lightweight working implementations, like an in-memory database that behaves like the real one but runs in-process. Spies are like stubs but also record how they were called, so you can check later. Dummies are objects that are never actually used—they just satisfy method signatures. For most everyday testing, you'll primarily use stubs and mocks, but knowing the others helps you pick the right tool for the job. One common mistake is overusing mocks when a stub would do, leading to brittle tests that break on innocent refactors.

Real-World Scenario: The Restaurant Kitchen

Let's ground these concepts in a familiar setting: a restaurant kitchen. Imagine you're the head chef testing a new recipe for a signature dish. You don't want to run to the pantry every time you need an ingredient; instead, you prepare sample ingredients ahead of time.

Stubs as Pre-Measured Ingredients

A stub is like having pre-measured, exactly portioned ingredients. When you need two cups of flour, you grab the pre-measured packet. In testing, if your code needs a database query result, you stub the database to return a specific row. This isolates the test from the real database's state. For example, a test for a user profile page might stub the user repository to return a known user object, so you can verify the page renders correctly without connecting to a live database.

Mocks as the Expediter's Order Sheet

The expo (expediter) in a kitchen calls out orders and expects the line cooks to respond exactly. If a cook fires the wrong dish, the expo stops the line. A mock is similar: it defines expected interactions and fails the test if they don't happen. Suppose your code should log a message when an order is placed. You mock the logger to expect a 'log' call with the order details. If the code forgets to log, the mock catches it.

Spies as Video Recording of the Line

A spy is like a security camera that records every move. After the service, you can review the footage to see how many times the grill was used. In testing, a spy wraps a real object and records calls—letting you assert later without predefining expectations. This is useful when you want to verify a side effect without making the test brittle to implementation details.

When to Use Stubs vs. Mocks: A Decision Framework

Choosing between a stub and a mock depends on what you're testing: the state of the system or its interactions. Here's a practical approach.

Use a Stub When You Care About Output

If your test is about what the system produces—a computed value, a rendered view, a transformed object—use a stub. Stubs provide controlled inputs so you can verify the output. For example, testing a function that calculates tax based on a user's country: stub the user service to return a user with country='DE', then assert the tax is correct. Stubs keep tests focused on results, not internal wiring.

Use a Mock When You Care About Communication

If the test's purpose is to verify that the system sends the right messages—calls a method with correct arguments, fires an event, or writes to a log—use a mock. Mocks are essential for testing command-like interactions where the outcome is a side effect. For instance, when testing an order service that sends an email confirmation, you mock the email service to expect a 'send' call with the order data. If the email isn't sent, the mock fails.

Common Pitfalls to Avoid

One frequent mistake is mock overuse. Teams often mock everything, leading to tests that break on every refactor. The rule of thumb: mock only across architectural boundaries (e.g., network calls, I/O), not within your own code. Another pitfall is overspecification—specifying exact call counts or argument details that aren't essential, making tests brittle. Use stubs for queries (read operations) and mocks for commands (write operations). This simple heuristic helps maintain test stability.

Step-by-Step: How to Create Effective Stubs

Creating a stub doesn't require a heavy framework. Here's a practical, language-agnostic process.

1. Identify the Dependency

Your code calls an external service or object. For example, a function that fetches a user's avatar from a remote API. That API call is the dependency you want to stub.

2. Define the Interface

Determine what method is called and what it returns. If the API returns a URL string, your stub should return a predictable URL like 'https://example.com/avatar.jpg'.

3. Implement the Stub

In most languages, you can create a simple class or use a mocking library's stub method. For example, in Python with unittest.mock, you'd write mock_api.get_avatar.return_value = 'https://example.com/avatar.jpg'. In JavaScript with Jest, jest.spyOn(api, 'getAvatar').mockReturnValue('https://example.com/avatar.jpg').

4. Inject the Stub

Replace the real dependency with the stub in your test. This is easier if your code uses dependency injection (passing dependencies as parameters) or a factory pattern. If your code hardcodes the dependency, you may need to refactor first—a worthwhile investment for testability.

5. Assert on Output

Your test should focus on what your code does with the stub's return value. For the avatar example, you might assert that the function returns a full URL or handles a null gracefully. Avoid asserting that the stub was called—that's for mocks.

Step-by-Step: How to Create Effective Mocks

Mocks require more care because they verify interactions. Here's a systematic approach.

1. Choose the Interaction to Verify

Identify the critical communication your code should perform. For an order service, the critical interaction might be calling the payment gateway's 'charge' method with the order total.

2. Set Up the Expectation

Using a mocking library, define what method should be called and with what arguments. For example, in Python: mock_gateway.charge.assert_called_once_with(amount=100). In Java with Mockito: verify(mockGateway).charge(100). Be precise but not over-precise—only specify arguments that are essential.

3. Optionally Define Return Values

If the mocked method returns a value (e.g., a payment confirmation), set the return value alongside the expectation. This keeps the test self-contained.

4. Execute the Code Under Test

Call the method you're testing. The mock will record interactions. If the expected call doesn't happen or happens with wrong arguments, the mock will throw an error at verification time.

5. Verify the Expectation

After the test, the mock checks that all expectations were met. Most libraries do this automatically in a teardown phase. If you're using a spy, you can manually assert on recorded calls.

Comparison Table: Stubs vs. Mocks vs. Fakes vs. Spies

TypePurposeVerificationUse Case
StubProvide canned answersNo interaction verificationTesting output/state
MockExpect and verify interactionsYes, exact calls and argsTesting commands/side effects
FakeWorking lightweight implementationNo extra verificationReplacing slow dependencies (e.g., in-memory DB)
SpyRecord calls for later inspectionOptional, after the factAuditing without upfront expectations

Real-World Scenario: Warehouse Logistics

Let's explore another analogy: a warehouse management system. Your code sends pick requests to a robot fleet. Testing this directly is expensive—you'd need real robots. Instead, you use test doubles.

Stubbing the Robot Status

Your code might check robot battery levels. A stub returns a fixed battery percentage, letting you test how the system reacts to low battery (e.g., sending a robot to recharge). You don't care how many times the status is queried; you just need a controlled value.

Mocking the Dispatch Command

When a new order comes in, your code dispatches a robot. To verify the dispatch command is sent correctly, you mock the dispatch service. The mock expects 'dispatch' to be called with the correct robot ID and destination. If the dispatch fails to happen, the mock fails the test.

Spying on the Logger

Your system logs every dispatch event. A spy wraps the logger, capturing all calls. After the test, you can assert that exactly two log entries were created, without predefining the expectation. This makes the test less brittle to logging implementation details.

Common Mistakes and How to Avoid Them

Even experienced developers fall into traps with test doubles. Here are the most frequent ones and how to sidestep them.

Mocking Everything

Newcomers often mock every dependency, including pure functions or in-process objects. This leads to tests that are tightly coupled to implementation and break on refactoring. Only mock across boundaries where you don't control the dependency (external APIs, databases). For your own code, prefer real objects or fakes.

Overspecifying Interactions

Specifying exact call counts or argument values that aren't essential makes tests brittle. For example, if you mock a logger to be called exactly once, but later you add debug logging, the test breaks. Use flexible matchers (e.g., 'any string') and only specify what matters for correctness.

Ignoring Side Effects

Stubs that return values but don't simulate side effects can miss bugs. For instance, a stub for a database might return a row but never simulate a connection error. Test both happy and error paths by creating stubs that throw exceptions when needed.

Not Cleaning Up State

If you use global mocks (e.g., monkey-patching), ensure you restore the original after each test. Most test frameworks offer setup/teardown hooks. Failing to clean up leads to test pollution where one test affects another.

Tools and Libraries Overview

While the concepts are language-agnostic, here are popular tools for implementing test doubles.

Python: unittest.mock

Part of the standard library, unittest.mock provides Mock, MagicMock, patch, and PropertyMock. It's flexible and widely used. Use Mock for stubs and MagicMock for objects with magic methods. patch helps replace attributes temporarily.

Java: Mockito

Mockito is the de facto standard for Java mocking. It uses a clean API: when(mock.method()).thenReturn(value) for stubbing, and verify(mock).method() for mocks. It integrates with JUnit and supports spies via @Spy.

JavaScript: Jest

Jest has built-in mocking via jest.fn(), jest.spyOn(), and jest.mock(). It's part of the Jest testing framework, making it easy to mock entire modules. For stubs, use mockReturnValue; for mocks, use toHaveBeenCalledWith.

Ruby: RSpec Mocks

RSpec Mocks is part of the RSpec ecosystem. It provides allow(...).to receive(...).and_return(...) for stubs and expect(...).to receive(...) for mocks. It also supports message expectations and spies.

When Not to Use Test Doubles

Test doubles are powerful, but they are not always the answer. Here are cases where you should avoid them.

Integration Tests

If you're testing that two systems work together correctly, use real dependencies or fakes. Mocking external services in integration tests can hide real problems like network timeouts or data format mismatches. Reserve mocks for unit tests.

Simple Logic

If your code is a pure function with no side effects, testing with real objects is straightforward. Introducing doubles adds unnecessary complexity. Only use doubles when the dependency is slow, non-deterministic, or hard to set up.

Overly Complex Mock Setup

If setting up a mock requires dozens of lines of expectations, your design might be too coupled. Consider refactoring to simplify interfaces or use dependency injection. A test that's hard to write often signals a design problem.

Conclusion

Test doubles are essential tools for writing fast, reliable unit tests. By understanding the differences between stubs, mocks, fakes, and spies—and applying the decision framework of output vs. interaction—you can choose the right double for each scenario. Remember the kitchen analogy: stubs are pre-measured ingredients, mocks are the expo's order sheet, and spies are security cameras. Avoid common pitfalls like mocking everything or overspecifying interactions. Start with stubs for read operations and mocks for writes, and always consider whether a simpler approach (like using a real object) works. With practice, these concepts become second nature, leading to a test suite that gives you confidence without slowing you down.

Frequently Asked Questions

Can a stub and a mock be the same object?

Yes. Many mocking libraries allow you to both stub a return value and set an expectation on the same object. For example, in Mockito, you can do when(mock.method()).thenReturn(value); verify(mock).method();. However, it's cleaner to separate concerns: use stubs for query methods and mocks for command methods, even on the same object.

What's the difference between a stub and a fake?

A stub is a lightweight object that returns hardcoded data. A fake is a working implementation that is simplified but functional. For example, an in-memory database that supports CRUD operations is a fake. A stub for the same database might only return a specific row without actually storing anything. Use fakes when you need realistic behavior without the overhead of the real system.

How do I choose between a mock and a spy?

Use a mock when you know exactly what interactions should occur and want to enforce them. Use a spy when you want to observe interactions without predefining expectations. Spies are useful for auditing or when the interactions are complex and you only need to verify some of them after the fact. In practice, spies are less common but handy for legacy code.

Do I need a mocking framework?

Not necessarily. You can create manual stubs and mocks by writing simple classes that implement the required interface. This is often clearer for simple cases. Frameworks become valuable when you have many dependencies or need advanced features like argument matchers, call counts, and order verification. Start simple, and adopt a framework when you feel the pain of manual doubles.

About the Author

This article was prepared by the editorial team for this publication. We focus on practical explanations and update articles when major practices change.

Last reviewed: April 2026

" }

Share this article:

Comments (0)

No comments yet. Be the first to comment!