Dependency Injection and Mocking
Learn how to isolate dependencies that our project uses, and then how to replace those dependencies at test time using Google Mock.
So far, our tests have called a function, and executed our function exactly as it would be executed in the real world. This is a useful default behaviour, as it means what we're testing is as close as possible to what our program actually does.
However, there are some situations where this is not desirable. For example:
- What if our function performs some high-impact action, like placing an order or calling an external API that bills us for usage? We don't want our test to make impactful changes outside of their scope or to cost money.
- What if a dependency that our function is using takes a really long time to calculate its result? We don't want our tests to be slowed down by some dependency that they're not directly interested in.
- What if our function has a different behaviour based on inputs from sensors, the current time, or the current date? We might want to test how our function behaves at weekends, but we want to our tests to be runnable and useful at all times, not just weekends.
The answer is to isolate the code we want to test from its dependencies. Google Mock is a library, bundled with Google Test, that lets you create "fake" versions of your dependencies, called mocks.
Dependency Injection
To use mocks, our code must be structured in a way that allows dependencies to be replaced. This is called Dependency Injection. The idea is to have your class depend on an abstract interface, not a concrete implementation.
This isn't just useful for testing - it makes our code more flexible in general.
For example, let's imagine we want our Greeter
to behave differently based on what the current day is. To set this up, let's have our Greeter
class be influenced by a DateProvider
service that it uses.
Step 1: Define an Interface
First, we define an abstract interface that our Greeter
will depend on. Our service will provide a getDayOfWeek()
function that returns an integer representing the current day:
greeter/include/greeter/IDateProvider.h
#pragma once
#include <memory>
// Abstract interface for a date provider
class IDateProvider {
public:
virtual ~IDateProvider() = default;
// 0=Sun, 1=Mon, ...
virtual int getDayOfWeek() const = 0;
};
// Factory function to create the real provider
std::unique_ptr<IDateProvider> createDateProvider();
Step 2: Create a Real Implementation
Next, we provide a "real" implementation that could be used in the production application. This implementation will use std::chrono
to get the current date:
greeter/src/ChronoDateProvider.cpp
#include <greeter/IDateProvider.h>
#include <chrono>
class ChronoDateProvider : public IDateProvider {
public:
int getDayOfWeek() const override {
using namespace std::chrono;
const auto now{system_clock::now()};
const weekday wd{floor<days>(now)};
// C++ weekday is 0-6 for Sun-Sat
return wd.c_encoding();
}
};
std::unique_ptr<IDateProvider> createDateProvider() {
return std::make_unique<ChronoDateProvider>();
}
Step 3: Inject the Dependency
Finally, we'll refactor the Greeter
class to use this ChronoDateProvider
by default, but it will also allow for a different provider to be injected at construction:
greeter/include/greeter/Greeter.h
#pragma once
#include <string>
#include <memory>
#include "IDateProvider.h"
class Greeter {
public:
// Default constructor uses ChronoDateProvider
Greeter();
// Greeter allows for a different dependency
// to be injected for testing/flexibility
explicit Greeter(
std::unique_ptr<IDateProvider> provider
);
std::string greet() const;
private:
std::unique_ptr<IDateProvider> date_provider_;
};
The greet()
method now uses the provider to get the day of the week:
greeter/src/Greeter.cpp
#include <greeter/Greeter.h>
// Forward declaration of the factory function
std::unique_ptr<IDateProvider> createDateProvider();
// Default constructor uses ChronoDateProvider
Greeter::Greeter() :
date_provider_(createDateProvider()) {}
// Constructor that takes a custom provider
Greeter::Greeter(
std::unique_ptr<IDateProvider> provider
) : date_provider_(std::move(provider)) {}
std::string Greeter::greet() const {
int day{date_provider_->getDayOfWeek()};
std::string day_str;
switch (day) {
case 1: day_str = "Happy Monday!"; break;
case 5: day_str = "Happy Friday!"; break;
default: day_str = "Have a nice day!"; break;
}
return day_str + " Hello from the Greeter class!";
}
Our GreeterLib
now has a new source and header file, so we should update our CMakeLists.txt
accordingly.
The library is now also using a modern C++ feature (std::chrono
from C++20) so we should specify that requirement:
greeter/CMakeLists.txt
cmake_minimum_required(VERSION 3.23)
add_library(GreeterLib)
target_compile_features(GreeterLib PUBLIC cxx_std_20)
target_sources(GreeterLib
PRIVATE
src/Greeter.cpp
src/ChronoDateProvider.cpp
PUBLIC
FILE_SET public_headers
TYPE HEADERS
BASE_DIRS "include/"
FILES
"include/greeter/Greeter.h"
"include/greeter/IDateProvider.h"
)
Writing the Mock Test
Now we can write a test that isolates Greeter
from the real ChronoDateProvider
.
Step 1: Create the Mock Class
In our test file, we create a mock implementation of IDateProvider
using Google Mock's MOCK_METHOD
macro. The arguments for MOCK_METHOD
are:
- The return type of the function we're mocking
- The name of the function we're mocking
- The argument types that it expects - eg
(int, bool, float)
. In our case,getDayOfWeek()
takes no arguments - Any qualifiers we need or want to use, such as
(const, override)
Mocking the getDayOfWeek()
function of an IDateProvider
would look like this:
tests/main.cpp
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <greeter/Greeter.h>
// Mock implementation of the IDateProvider interface
class MockDateProvider : public IDateProvider {
public:
// getDayOfWeek returns int, takes no arguments, and is
// const. We can also state that this is an override
MOCK_METHOD(int, getDayOfWeek, (), (const, override));
};
Step 2: Create the Mock Object
Inside our test case, we create an instance of our mock:
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <greeter/Greeter.h>
class MockDateProvider : public IDateProvider {
public:
MOCK_METHOD(int, getDayOfWeek, (), (const, override));
};
TEST(GreeterWithMockDate, GreetsForMonday) {
auto mock_provider{std::make_unique<MockDateProvider>()};
// ...
}
Step 3: Configure the Mock Object
Then, using EXPECT_CALL
, we tell the mock what to expect and how to behave.
In the following example, we're saying that we expect this test to eventually result in the getDayOfWeek()
function of our mock_provider
to be called a single time.
Additionally, we configure the mock such that the function should return 1
(corresponding to Monday) on that call:
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <greeter/Greeter.h>
// Mock implementation of the IDateProvider interface
class MockDateProvider : public IDateProvider {
public:
MOCK_METHOD(int, getDayOfWeek, (), (const, override));
};
TEST(GreeterWithMockDate, GreetsForMonday) {
auto mock_provider{std::make_unique<MockDateProvider>()};
EXPECT_CALL(*mock_provider, getDayOfWeek())
.WillOnce(testing::Return(1));
// ...
}
Google Mocks includes a huge suite of capabilities to configure how our mocks should behave. The official documentation has many examples.
Step 4: Inject the Dependency
Now, we create an instance of our Greeter
. We pass our mock to the constructor, causing greeter
to use it rather than its default ChronoDateProvider
:
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <greeter/Greeter.h>
class MockDateProvider : public IDateProvider {
public:
MOCK_METHOD(int, getDayOfWeek, (), (const, override));
};
TEST(GreeterWithMockDate, GreetsForMonday) {
auto mock_provider{std::make_unique<MockDateProvider>()};
EXPECT_CALL(*mock_provider, getDayOfWeek())
.WillOnce(testing::Return(1));
Greeter greeter(std::move(mock_provider));
// ...
}
Step 5: Assert the Outcome
Finally, we write our assertion. Our mock is always going to report the day as being 1
, so we ensure our greet()
function is behaving correctly if it reports the day as Monday:
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <greeter/Greeter.h>
class MockDateProvider : public IDateProvider {
public:
MOCK_METHOD(int, getDayOfWeek, (), (const, override));
};
TEST(GreeterWithMockDate, GreetsForMonday) {
auto mock_provider{std::make_unique<MockDateProvider>()};
EXPECT_CALL(*mock_provider, getDayOfWeek())
.WillOnce(testing::Return(1));
Greeter greeter(std::move(mock_provider));
EXPECT_EQ(
greeter.greet(),
"Happy Monday! Hello from the Greeter class!"
);
}
This test is now completely deterministic. It will always test the "Monday" logic, regardless of what day it's actually run on.
Step 6: Linking against GMock
Before building our tests, let's make sure we're still linking against the gmock
library.
We may also want to replace gtest_main
library with gmock_main
. This will create a main
function that sets up both Google Test and Google Mock. This is not strictly necessary in this example, but will be required if we use more advanced capabilities of Google Mock:
tests/main.cpp
cmake_minimum_required(VERSION 3.23)
find_package(GTest REQUIRED)
add_executable(GreeterTests main.cpp)
target_link_libraries(GreeterTests PRIVATE
GreeterLib
GTest::gtest
GTest::gmock
GTest::gtest_main
GTest::gmock_main
)
gtest_discover_tests(GreeterTests)
Step 7: Run the Tests
To ensure everything is working, let's configure and build the project:
cmake --preset default
cmake --build --preset default
If we run our application, we should see it log today's message:
./build/app/GreeterApp
Happy Friday! Hello from the Greeter class!
However, when we're running our tests, our injected dependency will convince the library that it's always Monday, giving us reliable testing behaviour:
ctest --preset default
1/1 Test #1: GreeterWithMockDate.GreetsForMonday ... Passed
100% tests passed, 0 tests failed out of 1
Parameterized Tests with Mocks
We can combine the power of mocking with the data-driven approach of parameterized tests.
Let's create a test that verifies the correct greeting for multiple days of the week:
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <greeter/Greeter.h>
#include <tuple>
class MockDateProvider : public IDateProvider {
public:
MOCK_METHOD(int, getDayOfWeek, (), (const, override));
};
// A parameterized test fixture
class GreeterDayTest :
public testing::TestWithParam<
std::tuple<int, std::string>
> {};
TEST_P(GreeterDayTest, GreetsCorrectlyForDay) {
auto [day_of_week, expected_greeting] = GetParam();
auto mock_provider{std::make_unique<MockDateProvider>()};
EXPECT_CALL(*mock_provider, getDayOfWeek())
.WillOnce(testing::Return(day_of_week));
Greeter greeter(std::move(mock_provider));
EXPECT_EQ(greeter.greet(), expected_greeting);
}
// Instantiate the test suite with our test data
INSTANTIATE_TEST_SUITE_P(
DayOfWeekGreetings,
GreeterDayTest,
testing::Values(
std::make_tuple(
1,
"Happy Monday! Hello from the Greeter class!"
),
std::make_tuple(
2,
"Have a nice day! Hello from the Greeter class!"
),
std::make_tuple(
5,
"Happy Friday! Hello from the Greeter class!"
)
)
);
Now, ctest
will run three separate, deterministic tests, one for each entry in our Values
list, all using the same mock-based logic.
cmake --build --preset default
ctest --preset default
...
100% tests passed, 0 tests failed out of 3
...
Summary
This lesson introduced three important concepts for writing more advanced tests:
- Dependency Injection: A design pattern where a class receives its dependencies from an external source rather than creating them itself. This decouples your classes and is a prerequisite for effective mocking.
- Mocking with Google Mock: We used Google Mock to create "fake" versions of our dependencies. The
MOCK_METHOD
macro generates a mock implementation of a method. - Controlling Behavior with
EXPECT_CALL
: We usedEXPECT_CALL
to define the expected behavior of our mock objects (e.g., what they should return when a method is called). This allows us to create specific, controlled scenarios for our tests.
Testing Executables
Learn the standard pattern for testing application logic by refactoring it into a linkable library