Introduction
When a unit test needs to isolate the code under test from a slow, unreliable, or hard-to-set-up dependency — like a network call, a database, or the current time — it replaces that dependency with a 'test double': a stand-in object that behaves predictably for the purposes of the test. Test doubles are what make it possible to unit test code that would otherwise require a live external system.
Cricket analogy: A batting coach practices a player's cover drive against a bowling machine set to a fixed line and length instead of a live express bowler like Bumrah, isolating the technique from unpredictable real conditions.
Explanation
There are five canonical kinds of test doubles, and it helps to distinguish them by purpose. A dummy is an object passed in only to satisfy a parameter list; it is never actually used by the code under test, such as passing None or a placeholder object where an argument is required but irrelevant. A stub provides canned answers to calls made during the test, returning fixed, predetermined data regardless of input — useful for controlling what a dependency 'says' without checking how it was called. A spy is like a stub but also records information about how it was called (arguments, call count), so the test can later assert on that interaction history. A mock is a spy that also has pre-programmed expectations about how it should be called, and the test explicitly verifies those expectations were met — mocks are 'verified' as part of the test itself. A fake is a working, simplified implementation of a dependency, such as an in-memory database used instead of a real one; it behaves correctly but takes a shortcut unsuitable for production, such as no persistence to disk.
Cricket analogy: In nets, a throw-down assistant lobs balls just to fill the session (a dummy), a bowling machine repeats the same delivery (a stub), a coach with a clicker counts every shot type (a spy), and a coach who checks the batter hit exactly ten cover drives as planned (a mock) differ from a net bowler genuinely bowling variations that behave like a real match (a fake).
In Python, the unittest.mock module provides Mock and MagicMock objects that can act as stubs, spies, or mocks depending on how they're used, and the patch() function temporarily replaces a real object (like a module attribute or class method) with a Mock for the duration of a test.
Cricket analogy: The BCCI's official practice-match rulebook lets a team temporarily swap its real opponent for a specified 'shadow XI' for the duration of a training session, exactly as patch() temporarily swaps a real object for a Mock.
Example
from unittest.mock import Mock, patch
# Code under test: it depends on an external payment gateway.
class OrderProcessor:
def __init__(self, payment_gateway):
self.payment_gateway = payment_gateway
def checkout(self, amount):
result = self.payment_gateway.charge(amount)
return result["status"] == "success"
# Using Mock() as a stub/spy: we control its return value and later
# inspect how it was called.
def test_checkout_returns_true_on_successful_payment():
# Arrange: a Mock stands in for the real payment gateway
fake_gateway = Mock()
fake_gateway.charge.return_value = {"status": "success"}
processor = OrderProcessor(payment_gateway=fake_gateway)
# Act
result = processor.checkout(100)
# Assert: check the behavior AND how the mock was used
assert result is True
fake_gateway.charge.assert_called_once_with(100)
# Using patch() to replace a real dependency during a test.
class ReportGenerator:
def build_filename(self):
import datetime
today = datetime.date.today()
return f"report-{today.isoformat()}.csv"
@patch("datetime.date")
def test_build_filename_uses_todays_date(mock_date):
# Arrange: force datetime.date.today() to return a fixed value
import datetime
mock_date.today.return_value = datetime.date(2024, 1, 1)
generator = ReportGenerator()
# Act
filename = generator.build_filename()
# Assert
assert filename == "report-2024-01-01.csv"Analysis
In the first test, fake_gateway is a Mock configured with a fixed return value, acting as a stub for the Assert phase's behavioral check (result is True), and then the same object is used as a spy/mock to verify the interaction with fake_gateway.charge.assert_called_once_with(100), confirming OrderProcessor called its dependency correctly. Notice the test never touches a real payment gateway, so it runs instantly and deterministically regardless of network conditions. In the second test, patch() temporarily replaces datetime.date so that today() returns a fixed date only for the duration of the test, letting us test date-dependent logic without it actually depending on the real calendar date — a common technique for making time-based code testable. Together, these examples show how test doubles let you isolate the unit under test from anything slow, non-deterministic, or external.
Cricket analogy: A commentator first confirms the batter's stroke result (four runs, a stub check) then reviews the replay to confirm the bowler bowled exactly the planned yorker (mock verification), all without a real match ball ever needing to be bowled live.
Key Takeaways
- The five canonical test doubles are dummy, stub, spy, mock, and fake.
- A dummy is never used; a stub returns canned data; a spy records calls; a mock verifies expected calls; a fake is a simplified working implementation.
- Python's unittest.mock.Mock() can act as a stub, spy, or mock depending on configuration and assertions.
- patch() temporarily replaces a real object with a Mock for the duration of a test.
- Test doubles isolate code from slow, unreliable, or external dependencies, keeping unit tests fast and deterministic.
Practice what you learned
1. Which of the five test double types is defined as a working, simplified implementation of a dependency, such as an in-memory database?
2. What distinguishes a mock from a spy?
3. In Python's unittest.mock, what does patch() do?
4. Which test double type is passed only to satisfy a parameter list and is never actually used by the code under test?
Was this page helpful?
You May Also Like
Unit Testing
Learn how to write isolated, fast unit tests for pure functions using the Arrange-Act-Assert pattern.
Integration and System Testing
Understand how integration testing checks components working together and system testing validates the whole application end-to-end.
Test-Driven Development
Learn the Red-Green-Refactor cycle that drives code design by writing tests before implementation.
Software Testing Fundamentals
An overview of why we test software, the testing pyramid, and the difference between black-box and white-box testing.
Related Reading
Related Study Notes in Software Engineering
Browse all study notesMicroservices Study Notes
Software Architecture · 30 topics
Software EngineeringTesting & TDD Study Notes
Software Testing · 30 topics
Software EngineeringDesign Patterns Study Notes
Software Design · 30 topics
Software EngineeringGit & Version Control Study Notes
Bash · 40 topics
Software EngineeringSystem Design Study Notes
Architecture · 40 topics