/
Mocks

Mocks

Suppose we want to validate some code that uses a driver. Traditionally, we would have to run the whole thing, both the code in question and the driver, on a physical microcontroller/board because the driver needs such an environment to function. With GoogleTest mocks, we can “abstract away” that driver and hardware dependency so that we can, theoretically, test on any platform that has a C/C++ compiler.

Speaking more generally, GoogleTest mocks give us the ability to execute the code that interests us without any of the finicky dependencies, allowing for our tests to be much more portable and targeted.

 

The Concept

Let’s suppose we have a class, A, that we want to validate. A depends on a hardware specific driver which is making testing difficult; maybe it’s restricting execution to a Nucleo board. Thus, we want to mock away that driver dependency.

First, we focus on the driver. If we want the driver to be mockable, then we need two versions: a real one that can actually interact with hardware and a mock. Both these versions will be derived from an abstract, parent driver.

Next, we look at our class, A. Any reference to the driver in A will be made via the abstract, parent driver.

This allows us to use either the real driver or the mock driver without having to refactor A before each switch. All we need to do is pass in the correct one and include the correct driver source files in our CMake.

The Implementation

Below is some example code that outlines the mocking principle. A .zip file containing all the source code for the example can be downloaded here:

Driver

Let’s start by writing our abstract driver. Notice how all the methods are pure virtual, meaning that they must be overridden when we define our subclasses.

// abstract_driver.hpp #pragma once class AbstractDriver { public: virtual int getFoo() = 0; virtual float getBar() = 0; virtual void setFoo(int i) = 0; virtual void setBar(int j, int k) = 0; };

 

Next, we can create our real driver, inheriting from the abstract driver. For the real driver, there is both a header and an implementation file.

// real_driver.hpp #include "abstract_driver.hpp" #pragma once class RealDriver : public AbstractDriver { public: RealDriver(); int getFoo() override; float getBar() override; void setFoo(int i) override; void setBar(int j, int k) override; };
// real_driver.cpp #include <iostream> #include "real_driver.hpp" using namespace std; RealDriver::RealDriver() { // constructor } int RealDriver::getFoo() { return 1; } float RealDriver::getBar() { return 2.3; } void RealDriver::setFoo(int i) { cout << "this is real setFoo()" << endl; } void RealDriver::setBar(int j, int k) { cout << "this is real setBar()" << endl; }

This is an example, so some dummy functions are used. In an actual application, drivers like this typically interact with the underlying hardware; imagine getFoo() reads from a register.

 

Our mock driver still inherits from the abstract driver, but this time there is only a header file. The “implementation” of the mock functions will be defined later.

Continuing the example from before, remember that the real getFoo() reads from a hardware register. This mock getFoo() will simulate that behavior without actually reading from such a register, allowing for the test to be run on any machine. This is how GoogleTest mocks help “abstract away“ dependncies.

ClassA

Here, we write our dependent class, the thing that uses the driver. Notice how all references to the driver are made via AbstractDriver.

ClassA contains the actual bits of code which we are trying to test. Specifically, we may be validating the functionality of doGetSeq() and doSetSeq().

Main Source Files

We will have two source files, one for normal runs and another for test sequences. This is where we pass in the desired driver depending on where we want to run our program.

For regular runs on the microcontroller, we will use the real driver.

 

For testing runs we will use the mock driver.

See how the “implementation“ of our mock functions is defined here. On lines 14 to 23, we state how the function should be called, the number of expected calls, and preset return values.

 

We can make our mock functions do a lot more. See the gMock Cookbook for more: gMock Cookbook

Related content