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

In order to understand mocking we need to understand different types of test doubles and what unit testing is. Later in this blog post I will go over some of the test doubles with examples. But let us start with unit testing:

In the purest form units tests are tests for one unit. How big or small a unit is, is often up for debate. It is often considered a class, as long as you adhere to Single responsibility - SOLID. It is important when doing unit tests not to test several units at a time. Meaning that units that work together or a tightly coupled should be out of the question. These are integration tests - not unit tests. Unit tests are often developed using Test-Driven development (TDD) or some components of this. However this post will not cover TDD. I recommend Kent Beck's book if you are interested in this topic:

Test doubles

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). This way the dependency of the objects are abstracted - 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. However 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. Then I inject a Always6DiceStub implementation (a stub test double). Stubs are small implementations that returns a hardcoded 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. 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 paramter - the number rolled and I 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. However 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. For every time I move, I add to the SpacesMoved variable and asserts 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 in my code.

So now we have used 3 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 let's 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.

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. In the above code a mock (or substitute as NSubstitute framework likes to call them) is created. It is then told to always return 6 when Roll() is called, just like the previous stub. Then 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 6. It is that easy to use a mocking framework. Next up is our spy:

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

So above is our test using a spy. Using NSubstitute I create a mock of the IBoardMap. I 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 a subject I will not touch. There is a huge debate about whether to use mocks or actual implementations. However there are some advantages and disadvantages to mocking vs creating actual implementations.

By using mocks you will have less implementations in your code base. You can read directly in your test what your implementations 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. I agree on this. 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 (now you know my bias). 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

In this chapter 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 response 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

We now have a basic understanding of mocks and what test doubles are. The examples that I have given in this post are of course very simple. This is what all mocking or testing posts/articles suffer from. Them being too simple. But I believe this post shows how mocks and other test doubles are related. But be confident that test doubles do scale.

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:

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