Table of contents
The history of test doubles
The content here is under the Attribution 4.0 International (CC BY 4.0) license
Daily, developers deal with mocks, stubs, fakes, dummies and other kinds of test double. Nevertheless, the word most often used to describe all of them is “mock”. Digging through GitHub code, blog posts and other resources, “mock” is the term that repeats most. Uncle Bob suggests this is because saying “mock that” is easier than naming the specific type being used.
Many libraries do the same, using only the word mock to cover every kind of test double. PHPUnit offers just two, Mock and Stub. In Kotlin the library of choice is called mockk, so the name itself declares the focus, and its API then covers the other kinds of double under that one word. It is understandable that practitioners end up saying “mock” for the whole family. The classic reason to reach for a test double is to isolate an external dependency such as:
- persistence
- file system access
- database access
- cloud storage
- caching systems
- third party libraries
- authentication
- time and date functions
- network access
- email services
- payment gateways
- message queues
- hardware devices
- configuration files or environment variables
Kent Beck in his iconic book about TDD named "TDD by Example" already set the bar for what we know today as mocks.
eXtreme programming has TDD embedded in its practices, which makes it a useful reference for test doubles. For XP teams, understanding the differences between them, and knowing when to reach for which, becomes a real advantage over teams that do not practise it.
Gerard Meszaros wrote xUnit Test Patterns, a reference work for the field of test code. He sets out how to refactor and maintain tests, and opens the discussion on what bad test code costs. I recommend it to any developer.
For TDD focused content you can check this dedicated page with detailed information about the practice from beginners to advanced concepts.
Test doubles
Test Doubles are essential tools in software testing, enabling developers to isolate and control the behavior of external dependencies. They are commonly used in Test-Driven Development (TDD) and come in various forms such as Dummies, Fakes, Stubs, Mocks, and Spies. This section will delve into the world of Test Doubles, providing definitions, examples, and use cases to enhance your understanding and application of these testing tools.
Dummy
Dummies are test doubles that have no implemented behavior but are used to fill method arguments. They can be useful when a method requires an object to be passed but does not use it
class AuthenticatedUser {
}
class MarsRover {
constructor(user: AuthenticatedUser) { }
execute(command: string) {
return '0:0:E';
}
}
describe('Dummy', () => {
it('mars rover should turn right when command is R', () => {
const rover = new MarsRover(new AuthenticatedUser());
const lastPosition = rover.execute('R');
expect(lastPosition).toEqual('0:0:E');
});
});
- user authentication (middleware)
- The test don’t care how it is used.
- usually it is required to make a given code to work, for example authentication.
- parameters of a function
Fakes
Fakes are test doubles that implement the same interface as the production code but provide canned or simplified responses. They are useful when the real implementation is slow or resource-intensive. For example, a fake email service could always return a success response, bypassing the need for actual email sending or a authenticated user that always return true or false regardless of its credentials.
class FakeAuthenticatedUser {
constructor(public name: string) {}
}
class FakeMarsRover {
constructor(private user: FakeAuthenticatedUser) { }
execute(command: string) {
if (this.user.name !== 'Jen') {
return '0:0:E'
}
return '0:0:N';
}
}
describe('Fake', () => {
it('mars rover should turn right when user is Jose', () => {
const rover = new FakeMarsRover(new FakeAuthenticatedUser('Jose'));
const lastPosition = rover.execute('R');
expect(lastPosition).toEqual('0:0:E');
});
it('user with name Jen should not move rover', () => {
const rover = new FakeMarsRover(new FakeAuthenticatedUser('Jen'));
const lastPosition = rover.execute('R');
expect(lastPosition).toEqual('0:0:N');
});
});
- A working implementation of an external library, it should have the same behavior.
- Sometimes libraries give fakes to help testing
- For example an in memory database.
- A specific rule from business can be used
Stubs
Stubs are test doubles that return predefined or canned responses to calls made during the test. They are useful when you want to isolate a unit of work and control the behavior of a specific function or method.
import axios from 'axios';
class Message {
constructor(public position: string) {}
}
class RoverApiService {
async fetchPosition(roverId: string): Promise<Message> {
const response = await axios.get('http://localhost:42422');
return new Message(response.data)
}
}
class MarsRoverStub {
constructor(private roverApi: RoverApiService) { }
async fetchRemotePosition(): Promise<string> {
const rover: Message= await this.roverApi.fetchPosition('my-rover')
if (rover.position === '0:0:N') {
return 'waiting';
}
return '0:0:E';
}
}
export class RoverApiServiceSuccess {
async fetchPosition(roverId: string): Promise<Message> {
return new Message('10:10');
}
}
describe('Stubs', () => {
it('mars rover should fetch current position from api', async () => {
const rover = new MarsRoverStub(new RoverApiServiceSuccess());
const position = await rover.fetchRemotePosition();
expect(position).toEqual('0:0:E');
});
});
- call to an api with typescript (stub values from json)
- Stubs returns hard coded values (predefined ones, success, error, or something different)
- does not record the number of times it was called
Mocks
Mocks are test doubles that record method calls, arguments, and return values, allowing for verification of specific behavior during the test.
class PositionRepository {
save(position: string) {}
}
class MarsRoverMock {
constructor(private mockedRepository: PositionRepository) { }
execute(command: string) {
this.mockedRepository.save('0:0:E');
return '0:0:E';
}
}
describe('Mocks', () => {
it('mars rover should store its position whenever a command is sent', () => {
const mockedRepository = {
save: jest.fn()
}
const rover = new MarsRoverMock(mockedRepository);
rover.execute('R');
expect(mockedRepository.save).toHaveBeenCalledWith('0:0:E');
});
});
- Mocks verify that something was called, with which arguments, and how many times.
- They can return values, but that is secondary and depends on the language, so reach for a stub when the return value is the point.
- Mocks are not the tool for asserting on returned values.
- It is also common to end up with many mocks when testing code that was written without tests first.
Spies
Spies are a test double that records how a method was used, so you can verify calls and arguments after the fact. They are useful when you want to check the interaction a system had without declaring expectations up front, which makes them less intrusive than a mock.
import axios from 'axios';
class Message {
constructor(public position: string) {}
}
class RoverApiService {
async fetchPosition(roverId: string): Promise<Message> {
const response = await axios.get('http://localhost:42422');
return new Message(response.data)
}
}
class MarsRoverSpy {
constructor(private roverApi: RoverApiService) { }
async fetchRemotePosition(): Promise<string> {
const rover: Message= await this.roverApi.fetchPosition('my-rover')
if (rover.position === '0:0:N') {
return 'waiting';
}
return '0:0:E';
}
}
export class RoverApiServiceSuccessSpy {
async fetchPosition(roverId: string): Promise<Message> {
return new Message('0:0:N');
}
}
describe('Spy', () => {
it('should verify the call to the api', async () => {
const roverApiServiceSuccess = new RoverApiServiceSuccessSpy();
const spyOn = jest.spyOn(roverApiServiceSuccess, 'fetchPosition');
const rover = new MarsRoverSpy(roverApiServiceSuccess);
const result = await rover.fetchRemotePosition();
expect(spyOn).toHaveBeenCalled()
// expect(result).toEqual('waiting')
});
});
A spy checks interactions without replacing the behaviour of the code under test.
Spies compared with mocks
A mock requires its expectations to be declared before the code runs. A spy records what happened and lets you assert afterwards, which fits the cases where the exact flow is not known in advance.
- In Jest, jest.spyOn wraps the original method, so the implementation still runs unless you replace it.
- Whether the original behaviour is preserved depends on the language and on the library.
Tools
- java
- JUnit, TestNG, or Spock
- Mockito, JMock, or PowerMock
- Hamcrest or Fest-Assert
- javascript
- jest, mocha, chai
Outside-in
- TDD by example Kent Beck - 2002
- xUnit 2007 - (Test Double Patterns - page 522)
- “My first and overriding piece of advice on this subject is: When there is any way to test without a database, test without the database!” - Gerard Meszaros, xUnit Test Patterns - chapter 13
- Growing Object Oriented Software Guided By Tests - 2009 (page 230)
- 2009 - http://www.mockobjects.com (https://web.archive.org/web/20230822035303/http://www.mockobjects.com)
- the era before xp but in 1999
- London group was discussing the effects that testing their code were having and adding getters and setters for testing felt wrong
- Clean c++ - chapter 2 - “Exclude Getters and Setters” as a good advice for testing
- the idea was to focus on the composition of components instead by Brad Crox - now it is known as dependency injection
- Connextra started to testout the idea of no getters in the code
- it forces delegation and composition
- this lead to emerging discussions about the messages between objects
- the refactoring done was showing some patterns with variables called expectedURL or expectedSomething
- They decided to call that mock
- Around same time Extreme Tuesday Club (XTC) was created
- A paper to XP2000 was submitted with the new approach
- Reception of the paper was mixed
- Nat Pryce built the same approach in Ruby
- later he ported the library to Java and joined the Connextra developers to add more features
- An OOPSLA paper followed, after they realised they were describing a language
- Mock Roles not objects paper was published
- 2009 - http://www.mockobjects.com (https://web.archive.org/web/20230822035303/http://www.mockobjects.com)
- Does TDD really lead to good design?
-
A Case for Outside-In Development
- The premise is that there is no reason for code to exist if it is not used in production
- Advantages
- inside-out
- early validation of business rules
- uncover business inconsistencies
- tackle to core of the system first
- inside-out
- Disadvantages
- waste effort
- build things that are not needed
- speculative development - start to think all possibilities that might not be used
- hard to do vertical slices starting from the domain
- rushed development, rush the front-end development after the back-end (or domain is done)
- lack of collaboration between front-end teams and back-end teams
- The front-end changes more often compared to the business rule in the back-end
- reason for ui to change
- aesthetic
- behavioral
- data - the information displayed to the end user
Test Doubles Quiz
Related reading
- Are true mocks evil?, on the Koksalan argument that overusing mocks pushes tests away from behaviour.
- Solitary tests vs sociable tests for how much of the real collaborator graph a test should include, which is the decision that determines how many doubles you need.
- A primer on stubbing APIs and testing services, a practical companion to the stub section above.
- TDD anti-patterns, where excessive setup and the mockery show up as test smells.
- AI and TDD, a match that can work, on the test-double decisions an LLM tends to get wrong.
References
- 2006 test doubles: https://martinfowler.com/bliki/TestDouble.html - mentioned xunit book - Mezaros is working on it
- 02 January 2007 mocks aren’t stubs - https://martinfowler.com/articles/mocksArentStubs.html
- xUnit Published May 1st 2007 by Addison-Wesley Professional
- Academic papers
-
Point/Counterpoint - Steve Freeman; Nat Pryce; Joshua Kerievsky. Also available through IEEE
- “Point Argument: Mock Objects: Find Out Who Your Friends Are,” by Steve Freeman and Nat Pryce. Mock objects help guide object-oriented programming by concentrating on what objects do, not what they are. “Counterpoint Argument: “TDD: Don’t Muck It Up with Too Many Mocks,” by Joshua Kerievsky. Routinely test-driving code with mock objects leads to premature object composition, hard-to-read and fragile code, and lost time. This department is part of a special issue on test-driven development
- Mocks, dummies, spies, fakes - testing your code - this is a blog post inspired by The LittleMocker post by Uncle Bob that shows the different types of test doubles in the testing code. The blog show case the different types in typescript.
- Architecture Patterns with Python: Enabling Test-Driven Development, Domain-Driven Design, and Event-Driven Microservices

