Categories
Blog Software Development

Test Driven Development: Test Doubles

The Web of Objects

Objects in an object-oriented program interact with each other. They depend on one another, call each other’s methods or hold references to each other. They collaborate to provide the functionality of the program. This is often called the web of objects.

On the other side, unit tests are used to test classes in isolation. But also in the unit test environment, they depend on other classes. These dependencies must be provided to create instances of the class under test or to call its methods. Then the classes that need to be created for the test have dependencies themselves, which must also be created. This way, the whole web of objects must be created just to test one class. The bigger the program becomes, the less this approach is feasible. Furthermore, using real objects in the test lacks the ability to verify the interaction between the objects.

This means that we need a way to untangle the web of objects. The class under test needs to be isolated until all of its connections to other objects are under the control of the test. Then the test can check the interactions with the other classes. This can be achieved using test doubles for the other objects that are provided via dependency injection.

Dependency Injection

Most objects depend on other classes to perform their tasks. They need a reference to these classes. They can create the references themselves by calling the constructors of the other class or by calling a factory method.

This approach creates a problem when you want to test this class. When the instance of this class is created for the test, then all of its other dependencies are also created and cannot be manipulated for the test.

To solve this, you can use dependency injections This basically means that the class under test does not create the references to its dependencies itself. Instead they are passed to the class from the outside. This can be done by using a constructor parameter or with a setter method.

Dependency Injection inside the Application

When dependency injection is used, the application must also provide all dependencies when it creates instances of a class. In this context dependency injection is also beneficial. It makes the dependencies between objects in a program transparent. And it also enables easier modification of the code. For instance, with dependency injection, you can easily change the logging technique that is used by the program. Instead of having to modify all the classes that use logging to fetch a reference to a different logging implementation, the new logging implementation is passed to the classes in just one place. Then the class does not have to be changed at all.

Example of Depencency Injection

Consider this C++ class:

class Car
{
private:
    Engine* engine;
    Tire* tires[4];

public:
    // Constructor
    Car()
    {
        engine = new CombustionEngine();
        for(int tireIndex = 0; tireIndex < 4; tireIndex++)
        {
            tires[tireIndex] = new StandardTire();
        }
    }

    void drive(int distanceInKm)
    {
        engine->start();
        for(int tireIndex = 0; tireIndex < 4; tireIndex++)
        {
            tires[tireIndex]->wearOff(distanceInKm);
        }
        engine->stop();
    }
};

The class Car creates its dependencies – the engine and the tires – in the constructor. This means that the test cannot verify the behaviour of the method drive. It could theoretically check the state of the tires after the test. But then the tires would need to become public members of the class. Also, the test cannot verify that the methods start and stop were called on the engine.

With dependency injection, the class receives the engine and the tires as parameters instead of creating them itself:

    ...

    // Constructor
    Car(Engine* engine, Tire* tires[4])
    {
        this->engine = engine;
        for(int tireIndex = 0; tireIndex < 4; tireIndex++)
        {
            this->tires[tireIndex] = tires[tireIndex];
        }
    }
    ...

Now the test can provide instances of Engine and Tire to the class Car. How these instances are created is the topic of the next section.

Test Doubles

Instead of using real objects as the collaborators of the class under test, a test should use _test doubles. These are classes that replace the real class by implementing the same interface as the original class, so that they can be used in their place. The _test doubles are provided to the class under test via dependency injection or as a parameter to a method call. There are several kinds of test doubles that you can prepar for a test.

  • The dummy is the simplest kind of double. It does not have any functionality at all. It just provides all the methods that the interface specifies. The dummy is useful when the object is only passed around as a parameter without checking anything.
  • The fake is a double that has a basic implementation of the original behaviour, so that the class under test can work with it.
  • The stub provides predefined answers to calls made to them during the test. Unlike the fake, it does not implement any logic.
  • The spy is a kind of stub that not only returns predefined answers, but also records all the calls made to them during the test. At the end of the test, you can check how often the stub’s methods were called
  • The mock is the most sophisticated test double. You can set expectations on the calls that are made on it during the test. At the end of the test, these expecations are checked automatically. The test fails if the mock’s methods were not called at all, too often or with incorrect parameters. The mock allows very precise verification of the behaviour of the class under test.

Because of the mock’s sophistication, it is the most useful test dummy for Test Driven Development. There are many mocking frameworks available that facilitate the creation of a mock. The mocking framework will create a mock class from an interface either by itself or with very little extra code. The mocking frameworks also provide methods to set expectations on the mock’s methods that are automatically evaluated at the end of the test.

The following example shows the unit tests for the class Car from the previous example. This example uses the mocking framework GoogleMock, which is part of the test framework GoogleTest.

class CarTest: public ::testing::Test {

protected:
    Car* testCar;
    TireMock* tireMock;
    EngineMock* engineMock;

    virtual void SetUp() {
        engineMock = new EngineMock();

        tireMock = new TireMock();
        Tire* tires[4];
        for(int tireIndex = 0; tireIndex < 4; tireIndex++)
        {
            tires[tireIndex] = tireMock;
        }

        testCar = new Car(engineMock, tires);
    }

    virtual void TearDown() {
        delete testCar;
        delete engineMock;
        delete tireMock;
    }

};

TEST_F(CarTest, testEngineStartedAndStoppedForDriving)
{
    EXPECT_CALL(*engineMock, start());
    EXPECT_CALL(*engineMock, stop());
    testCar->drive(12);
}

TEST_F(CarTest, testTiresWearOffDuringDriving)
{
    EXPECT_CALL(*tireMock, wearOff(12)).Times(4);
    testCar->drive(12);
}

The first test checks that the engine is started while driving. It does it by setting an expectation on the methods start and stop of engineMock. If these methods are not called during the test, then the test fails.

The second test checks that the tires are worn off during driving. The same mock object, tireMock, is passed into testCarfor all four tire references. Thus the test checks that wearOff is called four times on the tire object.

This example shows the basic features of mocks. Mocks can do even more for you. For example, you can let them return a specific value when a method is called, or you can check the parameters that are passed with more sophisticated expression.

Conclusion

Mock frameworks provide you with the tools to create the test doubles that you need. Also you can use them to create other kinds of test doubles, like spies or stubs. I recommend to choose a good mocking framework and make yourself familiar with it. You can find an overview about frameworks for many popular programming languages here.

To be able to use mocks in your tests, you will have to write your classes with dependency injection. If you develop your application test driven, then you will do this automatically because it is necessary to write the test. But it will also improve the quality of your application code, as dependency injection makes the dependencies between classes visible. And it provides you with the flexibility to exchange classes in the target code.

One reply on “Test Driven Development: Test Doubles”

Leave a Reply

Your email address will not be published. Required fields are marked *