Mocking

The unit tests we’ve written so far have all been pretty straightforward. While our Client class isn’t totally independent, its dependencies are all other data models and decorators that it can own and change at will. However, looking forward, we will want to persist client data in a database. Let's look at a few examples of how this can work and discuss how the design decisions we make impact the testability of the Client class.

Open up the scratchpad project and create a new header mocking.h file, where we’ll implement a dummy Client class to play around with.

mocking.h:

#ifndef MOCKING_H
#define MOCKING_H
#include <QDebug>
class Client { public: void save() { qDebug() << "Saving Client"; } };
#endif

In main.cpp, #include <mocking.h>, update the engine.load() line to load the default main.qml if it doesn’t already and add a few lines to spin up and save a dummy Client object:

engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

Client client;
client.save();

Build and run the app, ignore the window, and take a look at the Application Output console:

Saving Client

We have a way to ask a client to save itself, but it needs a database to save itself too. Let’s encapsulate our database management functionality into a DatabaseController class. In mocking.h, add the following implementation before the Client class. Note that you need to forward declare Client:

class Client;

class DatabaseController
{
public:
 DatabaseController()
 {
 qDebug() << "Creating a new database connection";
 }
void save(Client* client) { qDebug() << "Saving a Client to the production database"; } };

Now, edit the Client class:

class Client
{
 DatabaseController databaseController;
public: void save() { qDebug() << "Saving Client"; databaseController.save(this); } };

Back in main.cpp, replace the Client lines with the following:

qDebug() << "Running the production code...";

Client client1;
client1.save();
Client client2;
client2.save();

Now we create and save two clients rather than just one. Build, run, and check the console again:

Running the production code…
Creating a new database connection
Saving Client
Saving a Client to the production database
Creating a new database connection
Saving Client
Saving a Client to the production database

Okay, now we’re saving our clients to the production database, but we’re creating a new database connection for every client, which seems a bit wasteful. The Client class needs an instance of a DatabaseController to function, and this is known as a dependency.  However, we do not need the Client to be responsible for creating that instance; we can instead pass—or inject—the instance in via the constructor and manage the lifetime of the DatabaseController elsewhere. This technique of Dependency Injection is a form of a broader design pattern known as Inversion of Control. Let's pass a reference to a shared DatabaseController into our Client class instead:

class Client
{
 DatabaseController& databaseController;
public: Client(DatabaseController& _databaseController) : databaseController(_databaseController) { }
void save() { qDebug() << "Saving Client"; databaseController.save(this); } };

Over in main.cpp:

qDebug() << "Running the production code...";

DatabaseController databaseController;

Client client1(databaseController);
client1.save();
Client client2(databaseController);
client2.save();

Build and run the following:

Running the production code…
Creating a new database connection
Saving Client
Saving a Client to the production database
Saving Client
Saving a Client to the production database

Great, we’ve got a highly-efficient decoupled system architecture in place; let’s test it.

In mocking.h, add a pretend test suite after the Client class:

class ClientTestSuite
{
public:
 void saveTests()
 {
 DatabaseController databaseController;
 Client client1(databaseController);
 client1.save();
 Client client2(databaseController);
 client2.save();

 qDebug() << "Test passed!";
 }
};

In main.cpp, after saving client2, add the following to run our tests:

qDebug() << "Running the test code...";

ClientTestSuite testSuite;
testSuite.saveTests();

Build and run this:

Running the production code...
Creating a new database connection
Saving Client
Saving a Client to the production database
Saving Client
Saving a Client to the production database
Running the test code...
Creating a new database connection
Saving Client
Saving a Client to the production database
Saving Client
Saving a Client to the production database
Test passed!

Our test passed, fantastic! What’s not to love about that? Well, the fact that we’ve just saved some test data to our production database.

If you don’t already implement interfaces for the majority of your classes, you soon will after you start unit testing for this precise reason. It’s not used solely to avoid nasty side effects like writing test data to a production database; it allows you to simulate all kinds of behaviors that make unit testing so much easier.

So, let’s move our DatabaseController behind an interface. Replace the plain DatabaseController in mocking.h with a supercharged interface-driven version:

class IDatabaseController
{
public:
 virtual ~IDatabaseController(){}
 virtual void save(Client* client) = 0;
};
class DatabaseController : public IDatabaseController { public: DatabaseController() { qDebug() << "Creating a new database connection"; }
void save(Client* client) override { qDebug() << "Saving a Client to the production database"; } };

With the interface in place, we can now create a fake or mock implementation:

class MockDatabaseController : public IDatabaseController
{
public:
 MockDatabaseController()
 {
 qDebug() << "Absolutely not creating any database connections 
at all";
}
void save(Client* client) override { qDebug() << "Just testing - not saving any Clients to any
databases";
} };

Next, tweak our Client to hold a reference to the interface rather than the concrete implementation:

class Client
{
IDatabaseController& databaseController;
public: Client(IDatabaseController& _databaseController) : databaseController(_databaseController) { }
void save() { qDebug() << "Saving Client"; databaseController.save(this); } };

Finally, change our test suite to create a mock controller to pass into the clients:

void saveTests()
{
 MockDatabaseController databaseController;
 ...
}

Build and run this:

Running the production code...
Creating a new database connection
Saving Client
Saving a Client to the production database
Saving Client
Saving a Client to the production database
Running the test code...
Absolutely not creating any database connections at all
Saving Client
Just testing - not saving any Clients to any databases
Saving Client
Just testing - not saving any Clients to any databases
Test passed!

Perfect. By programming to interfaces and injecting dependencies, we can safely test in isolation. We can create as many mock implementations as we need and use them to simulate whatever behavior we want, enabling us to test multiple different scenarios. Once you get more involved in mocking, it really pays to use a dedicated framework like google mock, as they save you the hassle of having to write a bunch of boilerplate mock classes. You can easily mock the interface once using helper macros and then specify behaviors for individual methods on the fly.