Mock dependencies in C++ without DI

18 February 2022

mocks diagram

Do you want to mock your software’s dependencies so that you can unit test it easily?
Is refactoring with the Dependency Injection (DI) design pattern not a convenient solution or an option at all?
Then keep reading and I will show you a technique, that while it should normally be your last resort, it does allow you to mock most dependencies, without DI and typically, without changes to your code under test.

Mocking dependencies is a way to limit the scope of a (unit) test or, occasionally, make it easily runnable on a host computer without requiring the availability of a large system stack. For example, mocks come in handy when you do not want to use a real database in your tests or be able to run tests only on the target hardware. Let’s take a look at the following example to illustrate the usefulness of mocks.

class DcMotor {
public:
   int getSpeed();
   void stop();
};

class Car {
public:
   void controlSpeed() {
       if (mMotor.getSpeed() > 50) {
           mMotor.stop();
       }
   }
private:
   DcMotor mMotor;
};

How can controlSpeed() be tested? It takes no input and returns nothing. It does have a side effect, it should control a motor. However, it seems unlikely we will be able to verify this on our personal computers in a scalable, quick and convenient manner.

The idea behind mocking is that instead of the actual dependency, the code under test will invoke another object a “mock”, that we control and set expectations on. Sounds straight-forward and in an ideal world, where all code would follow the SOLID principles, it would be. Unfortunately, reality means challenges. We have software never written to be testable, frameworks deeply integrated into the business logic and many other factors that have resulted in code being tightly coupled to its dependencies.

Code tightly coupled to its dependencies does not inherently offer the flexibility to invoke alternative dependency implementations. The implementation it depends on is very specific. While that is not necessarily an issue from a strict business perspective, if for example we never plan to change those dependencies, it does usually impede testing. This is because making the code invoke a different implementation, for example a mock one, is not something the design has accommodated for.

Normally, you’d want to refactor so to break the coupling. As a rule of thumb, code that is loosely coupled to its dependencies is considered more maintainable and easier to evolve. One of the most popular design patterns that facilitates decoupling is Dependency Injection. The idea is that instead of your code getting hold of its dependencies, its dependencies are provided to it, preferably via constructor arguments. This practice is part of the Inversion of Control principle which promotes modular and extensible software. As you may have guessed, if we can inject dependencies, when testing we can provide a mock dependency instead of the “real” one.

If you are not already following this DI pattern, I’d normally suggest you to refactor your code. If this, for whatever reason, refactoring or DI is not an option, then you can go for the technique of “static mock injection”. However, I have to warn you, it comes with a cost!
Before we delve into it, let’s take a look at what you should normally go for if refactoring and DI are on the table.

⚠️ Disclaimer ⚠️

  • Treat all code as pseudocode
  • Assumptions
    • C++20
    • No particular coding guidelines
    • No domain restrictions
    • Room for optimization

Dependency Injection via an abstract interface

Your default choice when doing a Dependency Injection should be to understand what a dependency does and create an abstract interface out of it. Keep in mind the interface should not only be abstract in name, but also in practice. In other words, if you were to substitute your current dependency with a different one that offers the same functionality, your abstract interface should be able to reasonably accommodate both implementations.

struct Motor {
   virtual ~Motor()       = default;
   virtual int getSpeed() = 0;
   virtual void stop()    = 0;
};

class Car {
public:
   Car(Motor& motor) : mMotor{motor} {}

   void controlSpeed() {
       if (mMotor.getSpeed() > 50) {
           mMotor.stop();
       }
   }
private:
   Motor& mMotor;
};

Now the Car implementation does not depend on any concrete Motor. As long as we provide an object that specializes the Motor interface, Car is happy with it. This allows us to pass the “real” implementation when building our software for production and the mock implementation when we want to run our tests.

// Production implementation
class DcMotor : public Motor
{
public:
   int getSpeed() override;
   void stop() override;
};

// Mock implementation
struct MockMotor : public Motor
{
   MOCK_METHOD(int, getSpeed, (), (override));
   MOCK_METHOD(void, stop, (), (override));
};

To test this, using the GoogleTest framework, we would inject the mock and set our expectations on it before invoking the unit under test. Setting expectations on a mock object, means we specify the arguments as well as the times we expect it to be called. Additionally we may define any actions we would like to perform upon a mock method call, for example return a particular value or modify one of the output arguments.

TEST(CarTest, controlSpeedWhenSpeedTooHighWillStop) {
   MockMotor motor;
   Car car{motor};
   EXPECT_CALL(motor, getSpeed())
       .WillOnce(Return(100));
   EXPECT_CALL(motor, stop());
   car.controlSpeed();
}

TEST(CarTest, controlSpeedWhenSpeedLowWillNotStop) {
   MockMotor motor;
   Car car{motor};
   EXPECT_CALL(motor, getSpeed())
       .WillOnce(Return(49));
   EXPECT_CALL(motor, stop()).Times(0);
   car.controlSpeed();
}

Dependency Injection via a template type

Occasionally, you may want to avoid virtual functions and inheritance. DI is still possible by having the type of the dependency defined during compile-time using templates. There is still relatively tight coupling between your code and the implementation, however, since the type of the dependency can be selected externally, we can inject a mock object in our tests.

struct MockMotor {
   MOCK_METHOD(int, getSpeed, (), ());
   MOCK_METHOD(void, stop, (), ());
};

template<typename MotorType>
class Car {
public:
   Car(MotorType& motor) : mMotor{motor} {}
   void controlSpeed() {
       if (mMotor.getSpeed() > 50) {
           mMotor.stop();
       }
   }
private:
   MotorType& mMotor;
};

TEST(CarTest, controlSpeedWhenSpeedTooHighWillStop) {
   MockMotor motor;
   Car<MockMotor> car{motor};
   EXPECT_CALL(motor, getSpeed())
       .WillOnce(Return(100));
   EXPECT_CALL(motor, stop());
   car.controlSpeed();
}

TEST(CarTest, controlSpeedWhenSpeedLowWillNotStop) {
   MockMotor motor;
   Car car{motor}; // C++17 and up
   EXPECT_CALL(motor, getSpeed())
       .WillOnce(Return(49));
   EXPECT_CALL(motor, stop()).Times(0);
   car.controlSpeed();
}

Why not Dependency Injection?

Dependency Injection is straight-forward when it comes to decoupling code from dependencies and facilitates the use of mocks in tests. However, what happens when you cannot use DI?

This is typically not the case, but may occur if you cannot change the current API. For example, you may be dealing with a legacy component or code that is part of a larger framework that imposes certain design choices incompatible with DI. Alternatively, it may not be desirable to expose any dependencies outside of the class the uses them. Furthermore, some may consider injecting dependencies more complex rather than simply instantiating them within the context that requires them. Finally, there may be some concerns regarding the performance of virtual function calls.

There’s a bad and ugly side to the Dependency Injection alternative that will be demonstrated next, so think twice before steering away from DI.

Static mock injection: Overview

Let’s go back to the original example. The code we previously saw is now split into a header and an implementation file. This is done mainly for clarity when it comes to the CMake configuration we will show. It does not actually make any difference.

// car/include/Car.h
#pragma once
#include "DcMotor.h"
class Car {
public:
   void controlSpeed();
private:
   DcMotor mMotor;
};

// car/src/Car.cpp
#include "Car.h"
void Car::controlSpeed() {
   if (mMotor.getSpeed() > 50) {
       mMotor.stop();
   }
}

To have the implementation we want to test use a mock dependency, we have to decouple it from the “real” one.

We will not change any code, instead we will do this on the configuration level. When we build for production we will link to the working dependency, otherwise to the mock implementation. The cleanest way to achieve this, is by having the configuration targets of the code we want to test, depend only on APIs. In other words, on function or class declarations. You should depend on dependency definitions, only on the integration scope, for example targets that build a binary or combine multiple libraries.

add_library(dc_motor_api INTERFACE)
target_include_directories(dc_motor_api INTERFACE
    motor/include)

# Do not do depend on this for static mock injection to work 👇
add_library(dc_motor_coupled motor/src/DcMotor.cpp)
target_include_directories(dc_motor_coupled PUBLIC
    motor/include)
target_link_libraries(dc_motor_coupled PUBLIC dc_motor_api)

add_library(dc_motor motor/src/DcMotor.cpp)
target_link_libraries(dc_motor PUBLIC dc_motor_api)

add_library(car car/src/Car.cpp)
target_include_directories(car PUBLIC car/include)
target_link_libraries(car PUBLIC dc_motor_api)

In the CMake configuration, the target dc_motor_api contains only the declaration of the dependency, just the header file. The target that contains the actual implementation as well, is dc_motor and we should depend on this only for targets that we do not want to unit test. Car.cpp contains the code we want to test. Therefore the related target (car) should only depend on the API target (dc_motor_api).

# Production
add_executable(car_main car_main.cpp)
target_link_libraries(car_main PUBLIC
    car
    dc_motor)

# Tests
add_executable(car_test car_test.cpp)
target_link_libraries(car_test PUBLIC
    car
    mock_dc_motor)

In the integration scope, we should depend on both the code under test and a target containing an implementation. Our production target would depend on car and dc_motor while our test target would depend on car and mock_dc_motor.
Let’s take a quick look at the mock.

// mocks/MockDcMotor.h
class MockDcMotor
{
public:
   MOCK_METHOD(int, getSpeed, (), ());
   MOCK_METHOD(void, stop, (), ());

   static MockDcMotor& getInstance()
   {
       static MockDcMotor instance;
       return instance;
   }

private:
   MockDcMotor() = default;
};
add_library(mock_dc_motor mocks/MockDcMotor.cpp)
target_include_directories(mock_dc_motor PUBLIC mocks)
target_link_libraries(mock_dc_motor PUBLIC
    gtest
    gmock
    dc_motor_api)

MockDcMotor is an almost classic GMock class. It has two unusual characteristics. First, it does not inherit or have anything to do with the DcMotor dependency. There’s no inheritance. It is a class on its own.
Second, it is implemented as a singleton. We will utilize the singleton pattern to ensure that both the business logic as well as the tests use the same mock, without injecting anything.

// mocks/MockDcMotor.cpp
#include "MockDcMotor.h"
#include "DcMotor.h"

int DcMotor::getSpeed()
{
   return MockDcMotor::getInstance().getSpeed();
}

void DcMotor::stop()
{
   MockDcMotor::getInstance().stop();
}

This is where everything falls together; the mock implementation of the DcMotor dependency, which we link against when we build our tests. Please note: The MockDcMotor class you saw before, is not the “mock implementation”.
The infamous “mock implementation” resides in MockDcMotor.cpp and uses the MockDcMotor class. It defines the declarations from DcMotor.h and merely uses the singleton class MockDcMotor from the MockDcMotor.h header. Let’s make this clear. The production equivalent of MockDcMotor.cpp is DcMotor.cpp, in other words, the actual implementation of the DcMotor class. If it makes easier for you to see the equivalence, we could have had two DcMotor.cpp files. One in the production source directory and one in the test/mock directory.

In our tests when the Car class invokes a DcMotor, the implementation that gets called is the one in MockDcMotor.cpp. And since the singleton of the MockDcMotor class is being invoked, we can set expectations on it without injecting it.

#include "Car.h"
#include "MockDcMotor.h"

using ::testing::Return;

TEST(CarTest, controlSpeedWhenSpeedTooHighWillStop) {
   MockDcMotor& motor{MockDcMotor::getInstance()};
   Car car;
   EXPECT_CALL(motor, getSpeed())
       .WillOnce(Return(100));
   EXPECT_CALL(motor, stop());
   car.controlSpeed();
}

Our unit test starts by getting hold of the mock. MockDcMotor is the very same mock that is being used by the mock implementation of DcMotor that we saw before in MockDcMotor.cpp. Then we set expectations on it as normal and we invoke the code under test.
Despite no DI, we were able to mock the dependency and test the seemingly untestable controlSpeed() method.

Aside of the boilerplate code to create the mock implementation and some changes in the configuration, it was pretty convenient to use this technique to statically inject the mocks into the code under test. Will this always be the case though? Let’s take a look at a more complex case.

Static mock injection: The good

We have another car, CoolCar which owns multiple dependencies required for its implementation. Note that there are two Odometer instances and that there is no particular “meaning” behind the implementation. It is for illustration purposes, yet, I hope, adequately realistic.

// cool_car/cool_car.h
#include "gyroscope.h"
#include "logger.h"
#include "odometer.h"

class CoolCar {
public:
   void drive(int distance);
   void turn();

private:
   Gyroscope gyroscope_{};
   Odometer left_odometer_{};
   Odometer right_odometer_{};
   Logger logger_{};
};

// src/cool_car.cpp
void CoolCar::turn() {
   if (gyroscope_.getAngle() > 0) {
       logger_.send("Turning left");
   }
   else {
       logger_.send("Turning right");
   }
}
void CoolCar::drive(int distance) {
   logger_.send("Driving");
   while (true) {
       const auto left              = left_odometer_.getDistance();
       const auto right             = right_odometer_.getDistance();
       const auto traveled_distance = (left + right) / 2;
       if (traveled_distance > distance) { break; }
   }
}

We have the corresponding CMake configuration. There is nothing new to see here. We follow the same technique as before. Code that should be tested, namely cool_car.cpp, depends only on declarations. The definitions are linked in the integration scope, the cool_car_main target.

class CoolCarTest : public ::testing::Test
{
public:
   CoolCar coolCar_{};
   MockGyroscope& gyroscope_{MockGyroscope::getInstance()};
   MockLogger& logger_{MockLogger::getInstance()};
   MockOdometer& odometer_{MockOdometer::getInstance()};
};
add_library(mocks
    mocks/mock_gyroscope.cpp
    mocks/mock_logger.cpp
    mocks/mock_odometer.cpp)
target_include_directories(mocks PUBLIC mocks)
target_link_libraries(mocks
    gtest
    gmock
    gyroscope_api
    logger_api
    odometer_api)

add_executable(cool_car_test cool_car_test.cpp)
target_link_libraries(cool_car_test cool_car mocks)
configure_test(cool_car_test)

The same goes for our tests. Nothing new to see here, we provide mock implementations which relay the calls to the respective mock singletons. For convenience, since we will have multiple test cases, we create a test fixture CoolCarTest that gets holds of all the mocks and a CoolCar instance.

// src/cool_car.cpp
void CoolCar::turn() {
   if (gyroscope_.getAngle() > 0) {
       logger_.send("Turning left");
   }
   else {
       logger_.send("Turning right");
   }
}

// cool_car_test.cpp
TEST_F(CoolCarTest, turn_WhenAnglePositive_WillTurnLeft) {
   EXPECT_CALL(gyroscope_, getAngle())
       .WillOnce(Return(10));
   EXPECT_CALL(logger_, send("Turning left"));
   coolCar_.turn();
}

TEST_F(CoolCarTest, turn_WhenAngleNegative_WillTurnRight) {
   EXPECT_CALL(gyroscope_, getAngle())
       .WillOnce(Return(-10));
   EXPECT_CALL(logger_, send("Turning right"));
   coolCar_.turn();
}

There are no surprises when it comes to simple test cases. Verifying the functionality of turn() works as expected. Static mock injections work well when the mocking scenario is simple. But what happens when things are more complex or when a simple scenario like this one, gets combined with more intricate ones?

Static mock injection: The bad

It has been implied a lot so far, but if it was not clear, there are undesirable aspects of this technique. Let’s take a look at some bad ones first.

// include/odometer/odometer.h
#pragma once

class Odometer {
public:
   int getDistance();

private:
   int ticks_{0};
};

// mocks/mock_odometer.cpp
#include "mock_odometer.h"

#include "odometer.h"

int Odometer::getDistance() {
   static_cast<void>(ticks_); // Avoid unused variable warnings
   return MockOdometer::getInstance().getDistance();
}

Since the mock definitions share the header file with the production equivalents, there may be “implementation leftovers” that need to be accommodated. Here, we have a private member variable that is never used in the mock implementation and can emit a warning. This is not so terrible, but there could be more dependencies as private members that may have to be stubbed out. There is a risk you will need a lot of boilerplate code to get your mocks working.

// src/cool_car.h
void CoolCar::turn() {
   if (gyroscope_.getAngle() > 0) {
       logger_.send("Turning left");
   }
   else {
       logger_.send("Turning right");
   }
}
void CoolCar::drive(int distance) {
   logger_.send("Driving");
   while (true) {
       const auto left  = left_odometer_.getDistance();
       const auto right = right_odometer_.getDistance();
       const auto traveled_distance = (left + right) / 2;
       if (traveled_distance > distance) { break; }
   }
}

TEST_F(CoolCarTest,
 drive_WhenTraveledDistanceLargerThanTargetDistance_WillStop) {
   const auto target_distance = 10;

   EXPECT_CALL(odometer_, getDistance())
       .WillOnce(Return(target_distance * 2))
       .WillOnce(Return(target_distance * 2));
   coolCar_.drive(target_distance);
}

Did you think we were going to use singletons and get away without any drawbacks? Sadly, they exist.
As previously explained, each dependency instance calls the very same mock. We do take advantage of this behavior to set expectations on the same mock as the one used by our code under test, without having to inject it. There is only one mock instance we can set expectations on, regardless of how many times a mocked dependency gets instantiated. To put it simply, despite having two instances of Odometer in CoolCar, in our tests we only have one MockOdometer. As a result, we set expectations on a single object, while in the actual code there are two. CoolCar has the left_odometer_ and the right_odometer_ instances, but in our test we only have odometer_.
This inconsistency increases the complexity and decreases the readability of our tests, especially when our code handles multiple instances of a dependency.

Static mock injection: The ugly

Unfortunately this is not the only negative consequence of using a singleton. This was just the bad side of it. There is an ugly one too.

TEST_F(CoolCarTest, turn_WhenAnglePositive_WillTurnLeft) {
   EXPECT_CALL(gyroscope_, getAngle())
       .WillOnce(Return(10));
   EXPECT_CALL(logger_, send("Turning left"));
   coolCar_.turn();
}

TEST_F(CoolCarTest, turn_WhenAngleNegative_WillTurnRight) {
   EXPECT_CALL(gyroscope_, getAngle())
       .WillOnce(Return(-10));
   EXPECT_CALL(logger_, send("Turning right"));
   coolCar_.turn();
}

TEST_F(CoolCarTest,
 drive_WhenTraveledDistanceLargerThanTargetDistance_WillStop) {
   const auto target_distance = 10;
   EXPECT_CALL(odometer_, getDistance())
       .WillOnce(Return(target_distance * 2))
       .WillOnce(Return(target_distance * 2));
   coolCar_.drive(target_distance);
}

All these test cases look fine and they worked. To be exact, they worked individually. When combined, they may still work but, depending on the order, they can fail with a very unexpected error.

void CoolCar::drive(int distance) {
   logger_.send("Driving");
   while (true) {
       const auto left  = left_odometer_.getDistance();
       const auto right = right_odometer_.getDistance();
       const auto traveled_distance = (left + right) / 2;
       if (traveled_distance > distance) { break; }
   }
}

TEST_F(CoolCarTest,
 drive_WhenTraveledDistanceLargerThanTargetDistance_WillStop) {
   const auto target_distance = 10;
   EXPECT_CALL(odometer_, getDistance())
       .WillRepeatedly(Return(target_distance * 2));
   coolCar_.drive(target_distance);
}

test/cool_car_test.cpp:36: tried expectation #1: EXPECT_CALL(logger_, send(“Turning right”))…
Expected arg #0: is equal to “Turning right”
Actual: “Driving”
Expected: to be called once
Actual: called once - saturated and active
[ FAILED ] CoolCarTest.drive_WhenTraveledDistanceLargerThanTargetDistance_WillStop (0 ms)

Once we run all the test cases together, suddenly one of them starts failing. The error is very strange too. GMock complains it was expecting to get the send method of logger_ to be called once with the argument "Turning right". Instead, it is called with the argument “Driving”.
But wait a minute… "Turning right" is the argument of send in the turn method, not the drive method we are testing in the failing test case. We do not even set any expectations on logger when we test drive. So why did this show up now?

Well, since there is one instance of MockLogger expectations are “sticky” and are leaked between test cases. There is just one mock instance, the same instance in fact, for all the test cases. From the mock’s perspective, we expected it to be called twice. Once with the "Turning left" argument and once with the "Turning right" argument. Instead, we called it three times. In case you did not already notice, there is a rather uninteresting call to send with the argument "Driving" that we would typically omit from our unit tests, due to its lower importance.

class CoolCarTest : public ::testing::Test {
public:
   CoolCar coolCar_{};
   MockGyroscope& gyroscope_{MockGyroscope::getInstance()};
   MockLogger& logger_{MockLogger::getInstance()};
   MockOdometer& odometer_{MockOdometer::getInstance()};

   void TearDown() override {
     ASSERT_TRUE(
         testing::Mock::VerifyAndClearExpectations(&logger_));
   }
};

TEST_F(CoolCarTest,
 drive_WhenTraveledDistanceLargerThanTargetDistance_WillStop) {
   const auto target_distance = 10;
   EXPECT_CALL(logger_, send(_));
   EXPECT_CALL(odometer_, getDistance())
       .WillOnce(Return(target_distance * 2))
       .WillOnce(Return(target_distance * 2));
   coolCar_.drive(target_distance);
}

Thankfully, there are two workarounds for this. The first one involves “resetting” the mock after each test case. This way, expectations set in one test case, will not be leaked to the following ones. The second, is about being very explicit and specifying expectations for all the calls to the mock. Needless to say, this can clutter your test cases and further decrease their readability. Personally, I go for the first since I like my test cases to be very focused.
However, there is nothing stopping you from forgetting to add the necessary statements in TearDown. And then you get some errors that are extremely surprising. I have become accustomed to them and can quickly tell the culprit, but developers not familiar with the technique may be extremely baffled over the test failures.

Overall, with static mock injections the order of the test cases starts to matter, which is generally regarded as a very bad practice. Moreover, you may need to explicitly set uninteresting expectations on mocks, which hurts the readability and increases the scope of your test cases. You can get unrelated tests failing which may even be in test cases belonging to different .cpp files if they are all compiled into the same test binary.
As explained, there are workarounds but walking on thin ice is not usually a situation you want to be in.

Static mock injection alternative with templates

There is a variant of the static mock injection that circumvents the undesirable side effects of the singletons.

template <typename Motor>
class Car {
public:
   void controlSpeed() {
       const auto speed =
           (mLeftMotor.getSpeed() + mRightMotor.getSpeed()) / 2;
       if (speed > 50) {
           mLeftMotor.stop();
           mRightMotor.stop();
       }
   }

private:
   Motor mLeftMotor;
   Motor mRightMotor;
};

Now the dependency type, Motor, is determined during compile-time as the class gets turned into a class template. DI is not needed, however refactoring your code will be. In other words, it is not a drop-in replacement for the previous technique.

struct MockMotor
{
   static std::unordered_map<int, MockMotor*> mocks;

   MockMotor() : id{mocks.size()} { mocks[id] = this; }
   ~MockMotor() { mocks.at(id) = nullptr; }
   std::size_t id;

   MOCK_METHOD(int, getSpeed, (), ());
   MOCK_METHOD(void, stop, (), ());
};
std::unordered_map<int, MockMotor*> MockMotor::mocks{};

MockMotor is a bit more complex now. The mocked methods are here again, however, we also maintain non-owning pointers to all instances. The order of creation determines the identifier of each instance in the map or the vector you have stored them in. If you know when a dependency was instantiated, you can get hold of it.
Upon destruction, the respective non-owning pointer is set to null.

TEST(CarTest, controlSpeedWhenSpeedIsHighWillStop)
{
   // Car must be created first for mocks to exist
   Car<MockMotor> car;
   auto leftMotor  = MockMotor::mocks.at(0);
   ASSERT_TRUE(leftMotor); // Optionally assert the mock is not a nullptr
   auto rightMotor = MockMotor::mocks.at(1);
   ASSERT_TRUE(rightMotor);
   EXPECT_CALL(*leftMotor, getSpeed()).WillOnce(Return(51));
   EXPECT_CALL(*rightMotor, getSpeed()).WillOnce(Return(51));
   EXPECT_CALL(*leftMotor, stop());
   EXPECT_CALL(*rightMotor, stop());

   car.controlSpeed();
}

The biggest advantage of this static mock injection variant is avoiding leaky expectations. As long as you choose the right instance you are good to go. However, ensuring this can be inconvenient or complicated the bigger your test suite grows. If you come to think of it, it does not completely eliminate dependencies between test cases. The order still matters as it determines the index of the mock you should fetch.

Conclusion

Overall, my suggestion is for DI to be your default choice when decoupling your business logic from its dependencies and mocking them in your unit tests. Static mock injection can help you in some cases avoid refactoring, satisfy some domain constraints, “hide” dependencies inside your class and keep constructors simple. This technique will not work if your dependencies are declared and defined in a header file, since we will not be able to choose a different implementation during linking. In other words, you will not be able to mock any dependency. The title was slightly misleading. 😅
Static mock injection may make your life easier in the short run, but will not scale very well. It is good to know about, but should be used with caution and after having carefully ruled out the alternatives.

You can find most of the code on GitHub and this tutorial as a video on YouTube: