Skip to Content
Course content

231: Mocking Dependencies with Google Mock

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've been staring at this unit test for twenty minutes, and it's failing for the most annoying reason possible: it requires a physical temperature sensor to be plugged into the USB port. I'm working from home, and I don't have a thermal probe sitting on my desk. This is the classic "dependency nightmare."

The Hardware Headache

Here is the snippet of the code I'm trying to test. It's a simple ClimateController that's supposed to turn on a heater if the room drops below 20 degrees Celsius.

class TemperatureSensor {
public:
    float getTemperature() { 
        // Imagine this talks to a real hardware register
        return Hardware::readSensor(); 
    }
};

class ClimateController {
    TemperatureSensor& sensor;
public:
    ClimateController(TemperatureSensor& s) : sensor(s) {}
    bool checkHeatingNeeded() {
        return sensor.getTemperature() < 20.0f;
    }
};

If I try to write a test for checkHeatingNeeded(), I'm stuck. I can't force Hardware::readSensor() to return 15.0f just for my test. The ClimateController is tightly coupled to the concrete TemperatureSensor class. If I run this on a build server in a chilled data center, the test might pass; on my warm laptop, it fails. That's a flaky test, and flaky tests are worse than no tests at all.

Abstracting the Problem

I need a way to lie to the ClimateController. I want to tell it, "Believe me, it's freezing in here," without actually freezing the room. The first thing I have to do is break the hard link to the hardware. I'll introduce an interface.

class ITemperatureSensor {
public:
    virtual ~ITemperatureSensor() = default;
    virtual float getTemperature() = 0;
};

// The real one just implements the interface now
class TemperatureSensor : public ITemperatureSensor {
public:
    float getTemperature() override { return Hardware::readSensor(); }
};

// Now the controller takes the interface
class ClimateController {
    ITemperatureSensor& sensor;
public:
    ClimateController(ITemperatureSensor& s) : sensor(s) {}
    bool checkHeatingNeeded() {
        return sensor.getTemperature() < 20.0f;
    }
};

Now we're getting somewhere. But I could write a "Fake" class by hand, and that gets tedious quickly once you have ten different methods to simulate. This is where Google Mock (gmock) comes in.

Making the Mock Talk

Instead of writing a whole fake class, I'll let gmock generate the implementation for me. I'll create a MockTemperatureSensor. I'm using the MOCK_METHOD macro here—it tells gmock, "Whenever this function is called, don't actually do anything; just record that it happened and return a default value."

#include "gmock/gmock.h"

class MockTemperatureSensor : public ITemperatureSensor {
public:
    MOCK_METHOD(float, getTemperature, (), (override));
};

Let's try to use this in a test. I'll use ON_CALL to define what the mock should do when it's asked for the temperature.

TEST(ClimateControllerTest, TurnsOnHeaterWhenCold) {
    MockTemperatureSensor mockSensor;
    ClimateController controller(mockSensor);

    // I'm telling the mock: "When getTemperature is called, return 15.0"
    ON_CALL(mockSensor, getTemperature()).WillByDefault(testing::Return(15.0f));

    EXPECT_TRUE(controller.checkHeatingNeeded());
}

This works! The test passes because the controller thinks it's 15 degrees. But there's a catch. ON_CALL is passive. It's a suggestion. If the ClimateController had a bug and never called getTemperature(), the test might still pass or fail in ways that don't actually prove the logic is correct. I don't just want the right answer; I want to ensure the controller is actually asking the sensor for the data.

Verifying the Side Effect

To move from "stubbing" (providing values) to "mocking" (verifying behavior), I'll swap ON_CALL for EXPECT_CALL. This changes the test from "If this happens, do that" to "I expect this to happen, and if it doesn't, fail the test."

TEST(ClimateControllerTest, CallsSensorExactlyOnce) {
    MockTemperatureSensor mockSensor;
    ClimateController controller(mockSensor);

    // This will fail the test if getTemperature() is NOT called exactly once
    EXPECT_CALL(mockSensor, getTemperature())
        .Times(1)
        .WillOnce(testing::Return(15.0f));

    controller.checkHeatingNeeded();
}

I've noticed that as I build more complex mocks, I sometimes forget to add the (override) specifier in the MOCK_METHOD macro. If you do that, the compiler might not warn you if the base class interface changes, and your mock will suddenly stop being called because the signatures no longer match. Always include it.

Now I can simulate any scenario—sensor timeouts, extreme heat, or erratic readings—without ever leaving my chair or touching a piece of hardware.




📋 Practical Task

Exercise: Simulating a Payment Gateway Failure in an Order Processor

You are building an OrderProcessor class that depends on an IPaymentGateway. The processor should only mark an order as "Paid" if the gateway's processPayment method returns true. If it returns false, the order should be marked as "PaymentFailed".

Your task:

  1. Create the IPaymentGateway interface with a method virtual bool processPayment(double amount) = 0;.
  2. Create a MockPaymentGateway using Google Mock.
  3. Write a unit test for the OrderProcessor that uses EXPECT_CALL to simulate a failed payment (returning false).
  4. Verify that the OrderProcessor correctly transitions the order status to "PaymentFailed" when the mock returns false.

Ensure your mock specifically verifies that processPayment was called with the correct order amount.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.