Mocks, dummies, spies, fakes - testing your code

Last updated Apr 28, 2024 Published Nov 29, 2020

The content here is under the Attribution 4.0 International (CC BY 4.0) license

Testing code has different definitions and sometimes can mean different things to different people, however, the hidden convention that sticks until today is “mocking”, regardless of the type of test double. In 2007, Martin Fowler blogged about the differences between mocks and stubs [1], despite the efforts in making the differences apparent for developers, “mock” is what is widely used in the day-to-day conversations. The content that follows is a collection of easy-to-come-back definitions of mocks, dummies, stubs, spies and fakes with code examples in typescript.

Mocks/Test doubles

Mock is frequently used interchangeably with the term test double, mock is often used for people who didn’t read the paper definition of mock. Then, developers got used to the term mock instead of test double. According to Uncle Bob, mocks refer to the whole family of objects that are used in tests [2]. On the other hand, Steve Freeman and Nat Pryce[3] define this technique as “substitutes” or “mock objects”. The following picture from Santos et al. [4] depicts visually the different types of test doubles:

Venn diagram of Test Doubles from Agile Technical Practices Distilled

Despite the family of mocks and its definition, the goal of using this technique is to test a unit of code in isolation, without touching its dependencies. Spadini et al. [5] investigated empirically three open source projects and one industrial project and found that developers mock external dependencies in the code, the projects use were using Java.

In that sense, the sections that follow are a walk-through of each of the test-double with examples in Typescript and Jest. Some snippets can be run directly on this page through CodePen, for such, modifications to the code had to be made and the runner had to be replaced by jest-lite. The Mars Rover kata was taken as the base example, it is recommended to be familiar with the kata as it will make the examples easier to digest, but it is not compulsory.

Dummies

You pass in something, and you don’t care who it is used, often the object is not used at all.

Dummy in typescript

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 doesn’t care how it is used.
  • Usually, it is required to make a given code work, for example, authentication.
  • parameters of a function

Try to edit the code yourself:

See the Pen Untitled by Marabesi (@marabesi) on CodePen.

Stub

Opposed to dummies, stubs are objects created in a way that you don’t care how they are used. For example, to tricky an authorization to test if the user ca/can’t do certain actions in the system.

Stub in typescript

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 return hard-coded values (predefined ones, success, error, or something different)
    • does not record the number of times it was called

See the Pen Stub by Marabesi (@marabesi) on CodePen.

Spies

To assert that a method was called by the system under test, as the post by Uncle Bob [2]: “You can use Spies to see inside the workings of the algorithms you are testing”.

Spy in typescript

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()
    });
});

See the Pen Test-double: spy example by Marabesi (@marabesi) on CodePen.

True mocks

Is interested in the behavior, instead of return of functions. It cares about which functions were invoked, with what arguments and how often.

True mock in typescript

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');
    });
});

See the Pen true mocks by Marabesi (@marabesi) on CodePen.

Fakes

Fakes have business logic, so they can drive the system under test with different sets of data.

Fakes in typescript

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 should have the same behavior.
    • Sometimes libraries give fakes to help with testing
    • For example an in-memory database.
    • A specific rule from business can be used

See the Pen Test-double: fakes by Marabesi (@marabesi) on CodePen.

Tech excellence - TDD anti-patterns

In the session with Tech Excellence, we touched on the subject of test doubles as well, the following link is available for the talk:

Changelog

  • Apr 28, 2024 - Added code examples to all sections

References

  1. [1]M. Fowler, “Mocks Aren’t Stubs,” 2007 [Online]. Available at: https://martinfowler.com/articles/mocksArentStubs.html. [Accessed: 29-Nov-2020]
  2. [2]R. C. Martin, “The Little Mocker,” 2014 [Online]. Available at: https://blog.cleancoder.com/uncle-bob/2014/05/14/TheLittleMocker.html. [Accessed: 29-Nov-2020]
  3. [3]S. Freeman and N. Pryce, “Growing Object-Oriented Software, Guided by Tests,” 2009 [Online]. Available at: https://www.amazon.com/Growing-Object-Oriented-Software-Guided-Tests/dp/0321503627. [Accessed: 08-May-2021]
  4. [4]P. M. Santos, M. Consolaro, and A. Di Gioia, Agile Technical Practices Distilled: Become Agile and Efficient by Mastering Software Design. Packt Publishing, Limited, 2019 [Online]. Available at: https://books.google.es/books?id=sBT-xgEACAAJ
  5. [5]D. Spadini, Aniche Maurı́cio, M. Bruntink, and A. Bacchelli, “To mock or not to mock? an empirical study on mocking practices,” in 2017 IEEE/ACM 14th International Conference on Mining Software Repositories (MSR), 2017, pp. 402–412.