The history of test doubles

Last updated Aug 16, 2025 Published Nov 5, 2023

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.

Cover of the book "Extreme Programming Explained: Embrace Change, 2nd Edition (The XP Series) 2nd Edition"

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.

Cover of the book "XUnit Test Patterns: Refactoring Test Code"

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
      • 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
  • 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
    • 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

1. What do test doubles do in software testing?
2. What are dummies used for in test doubles?
3. What are fakes used for in test doubles?
4. What do mocks record in test doubles?
5. What are spies used for in unit testing?

References

You also might like