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.

Greg Filak
Published

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 used EXPECT_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.
Next Lesson
Lesson 51 of 55

Testing Executables

Learn the standard pattern for testing application logic by refactoring it into a linkable library

Have a question about this lesson?
Answers are generated by AI models and may not be accurate