Why do we use mocking for unit tests? and the use of test-doubles - Updated 2022

In order to understand how and why we use mocks for tests we need to understand different types of test doubles (implementations used for testing) and what unit testing is. We will start with unit testing and then move on to different types of test doubles - with examples.

In the purest form, unit tests are tests for a unit, how big or small a unit is, is up for debate. It is often considered a class, but it could also be considered just a method. However in object oriented programming we often use a class since a class may have state, in order for us to find faults in the class we might have to call several methods after one another. Such as for testing a List class you will first have to add something to the list, before you can test the removal functionality. It is important when writing unit tests not to test several units at a time, meaning that units that work together or are tightly coupled should be out of the question. These are integration tests - not unit tests - integration tests aim to test several components together whereas unit tests are tests of a unit in isolation. Isolation can be from other classes, but it can also be from IO, Datases, API calls and so on. Unit tests are often developed using Test-Driven development (TDD) or some components of this. This post will not cover TDD. I recommend Kent Beck's book if you are interested in this topic:

Test doubles

In order to test our units in isolation we need to decouple (isolate) them. Decoupling is often accomplished by some sort of dependency injection. For example plain old usage of constructors - or another way of "setting" a dependency. The great thing about this, is that we can create test-specific implementations (test doubles). With this approach the dependencies become abstract - and do as they are instructed under the given test.

Below is an example of a stub implementation. For my examples I use the language C# and I use the unit test framework Nunit, but they will be easy to read if you have a C++ or java background. I aimed at making my examples simple so that anyone with any object oriented programming background can read them. Below I am going to create a very small implementation of a board game:

public class BoardGame : IBoardGame
{
    private IDice _dice;

    public BoardGame(IDice dice)
    {
        _dice = dice;
    }

    public int RollDice()
    {
        return _dice.Roll();
    }
}

So far the only thing you can do in the BoardGame is roll the dice. This relies on a dependency injected through the BoardGame constructor. To test this we make a small test to make sure that our BoardGame returns whatever the dice's result is:

[Test]
public void BoardGameReturns6WhenDiceReturns6()
{
    var boardGame = new BoardGame(new Always6DiceStub());
    Assert.AreEqual(6, boardGame.RollDice());
}

private class Always6DiceStub : IDice
{
    public int Roll()
    {
        return 6;
    }
}

In my test above I create a new BoardGame object, then I inject a Always6DiceStub implementation (a stub test double). Stubs are small implementations that return a hardcoded (canned) answer, which makes them great for this. Had I made an implementation that actually returned a random number, then I would have had to assert a range or my test would become flaky due to the randomness. The stub makes sure that I always get the number 6 back. I do not have any other implementation of my dice other than the stub, I can fully test my BoardGame class with no actual implementations so far.

The next method for my BoardGame will be the MovePlayer() method. This method Will take a number as a parameter - the number rolled and for simplicity we will move that far in the game. For this I introduce the BoardMap, which will keep track of which position the different players are at. But for now there is only one player:

private IDice _dice;
private IBoardMap _boardmap;

public BoardGame(IDice dice, IBoardMap boardmap)
{
    _dice = dice;
    _boardmap = boardmap;
}

public void MovePlayer(int spaces)
{
    _boardmap.MovePlayer(spaces);
}

The above is the same BoardGame as before. But with a new method and dependency for the BoardMap. You probably noticed that the MovePlayer() method does not return anything. Then how do we test this? This is where the spy test double comes into play:

[Test]
public void BoardGameCanMoveSpaces()
{
    var boardMapSpy = new BoardMapSpy();
    var boardGame = new BoardGame(new DiceDummy(), boardMapSpy);
    boardGame.MovePlayer(2);
    boardGame.MovePlayer(5);
    boardGame.MovePlayer(3);
    Assert.AreEqual(10, boardMapSpy.SpacesMoved);
}

private class BoardMapSpy : IBoardMap
{
    public int SpacesMoved = 0;

    public void MovePlayer(int spaces)
    {
        SpacesMoved += spaces;
    }
}

private class DiceDummy : IDice
{
    public int Roll()
    {
        throw new NotImplementedException("Dummy implementation");
    }
}

Above I have created a spy test double in order to record what is sent to the spy. A spy test double records the input and at the end can give a report on this. Every time I move, I add to the SpacesMoved variable and assert that the sum is correct.

I still have a dice that needs to be injected into the constructor. For this I could just have used the value null. But since I don't like null values and the dependency could have been required to be there, instead of using null I create a dummy implementation. Which is another test double. This type of test double does nothing but make sure that I fulfill the contracts of my code.

So now we have used three different types of test doubles. The title of this post has Mock in it. We will cover this next.

Mocks

I often use the term "mocking" instead of test doubles. Why? Because I use a mocking framework for almost all my test doubles. With a strong mocking framework you do not have to create the above test doubles. A mocking framework lets you create mocks - which is a special type of test double. For this I will be using the framework NSubstitute, this is my favourite but there are many others that can do about the same.

I will go through the previous examples and instead of using test doubles, I will use mocks:

[Test]
public void BoardGameReturns6WhenDiceReturns6WithMocks()
{
    var dice = Substitute.For<IDice>();
    dice.Roll().Returns(6);
    var boardGame = new BoardGame(dice);
    Assert.AreEqual(6, boardGame.RollDice());
}

Above is the same example as my first test. However instead of using a stub, we use a mock acting as a stub. A mock (or substitute as NSubstitute framework likes to call them) is created, it is then instructed to always return six when Roll() is called, just like the previous stub. Next a new BoardGame is created and the dice Mock injected. As before the boardGame.Rolldice() method is called and it is asserted that it returns six. That was an example of making a stub using a mocking framework, next up is our spy test double:

[Test]
public void BoardGameCanMoveSpacesMock()
{
    var dice = Substitute.For<IDice>();
    var boardMap = Substitute.For<IBoardMap>();
    var boardGame = new BoardGame(new DiceDummy(), boardMap);
    boardGame.MovePlayer(2);
    boardGame.MovePlayer(5);
    boardGame.MovePlayer(3);
    boardMap.Received().MovePlayer(2);
    boardMap.Received().MovePlayer(5);
    boardMap.Received().MovePlayer(3);
}

Above is our test using a spy. Using NSubstitute I create a mock of the IBoardMap and then proceed to give it the same values as before, and at the end assert that it received these calls. I also create a substitute for the dice to use a dummy - which does nothing but make sure I can fill out the constructor.

So now we have replaced all our other test doubles with a mock counterpart. Did the code get better or worse? that is up to the person who writes the code, some like mocks, others actual implementations. I will go over some advantages and disadvantages to mocking vs creating specific implementations for testing.

By using mocks you will have less implementations in your code base. You can read directly in your test what your implementation does. But does this actually cause less code? You may save some curly brackets but you will still need to define what should be returned or spied upon for each test. Some say that using actual implementations feels more native. There is a learning curve when introducing a mocking framework. If you work in a team environment the whole team will have to be able to understand the framework (it at least has to be readable). This is an investment, like any other investment in a given framework.

Mocking is a powerful tool, and you can do many things with it. Many frameworks are immense in features. But remember that you can always do the same thing using an actual implementation. I have been using mocks for many years now and it is still what I prefer. But this is only when working with C#. When I code Java for example I do not know any mock libraries, therefore I use the other types of test doubles.

Types of test doubles

Here I will go over the different types of test doubles and give a quick summary. These are the building blocks for creating great unit tests. Some unit tests do not need test doubles of course - but most do! The Test double term was created by Gerard Meszaros - you can read more about it in his own article. Here is my take on it:

  • Dummy: An implementation used just to fulfill a contract. Such as a constructor or method. Under the given testcase the dummy implementation is not called.
  • Stub: An implementation with a built-in response. Often used to test a specific returned value from a dependency. This makes it easy to avoid randomness or perhaps get a specific error code (which might be hard to trigger).
  • Spy: The spy records whatever is sent to it so that we later can make sure we made the right calls. This is often done to make sure that the dependency is called correctly - and under the right conditions. The spy can also make a report on how it was called. Which makes the report assertable. It is often used for void methods.
  • Mock: A mock relies on a mocking framework. Instead of creating implementations of Dummies, Stubs and Spies we can use a mock. A mock can therefore be any of the 3. With some frameworks you can also make most fake test doubles. But in itself, the mock is also a test double.
  • Fake: A fake is a partial implementation - and was not covered in my examples. It is often used to simulate file systems, databases, http requests and responses and so on. It is not a stub since it has more logic to it. It might keep state of what is sent to it (inserted into the database) and return this on request.

Closing notes

I hope you now have a better understanding of mocks and what test doubles are. The examples that I have given in this post are of course very simple. But I believe this post shows how mocks and other test doubles are related.

Unit tests using test doubles let us test our code in isolation - under conditions that we are in control of. We can abstract away any state, IO, databases or the like using test doubles. Another thing that unit tests help us with is decoupling our code. Separating the responsibility of our different classes. If you wish for some further reading I recommend the below books:

Disclosure: Bear in mind that some of the links in this post are affiliate links and if you go through them to make a purchase I will earn a commission. Keep in mind that I link these companies and their products because of their quality. The decision is yours, and whether or not you decide to buy something is completely up to you.

I hope you liked the post, let me know what you think in the comments down below!