Mocking virtual functions with gMock

Sandor Dargo - Mar 2 '22 - - Dev Community

In this mini-series we are going to discover mocking with gMock, the probably most widely used C++ mocking framework.

I think that practical discussions should start with theoretical ones. In order to understand something from a practical point of view, we should understand the theoretical background.

This is important because we will not simply try to mimic examples, but we will try to do things that make sense even from a birds-eye view.

What are mocks and how do we get them wrong?

It seems evident that we want to speak about mocks when we want to learn about gMock. First, we should understand what mocks are and what are the competing concepts.

Mocks are objects that

  • are needed in a system under test and
  • that are implementing the same interface as the original objects.

Mocks can be used to observe and verify behaviour when we cannot verify something on the class under test and it has side effects; such as invoking methods on our mocks.

In other words, mocks are objects with pre-defined expectations on what kind of calls they should receive.

As we are going to see, mocks in gMock do fulfil this idea, but they do more. They also act as stubs. Stubs can be configured to respond to calls from the system under tests with the values or exceptions predefined.

Stubs are come in handy when you have to tests objects depending on external calls (such as calls to networks, databases, etc.). Stubs might not only be able to send these canned answers but they can also have a memory so that they "remember" what they sent. Such stubs might be referenced as spies. You might even define that the first 3 answers should be different from what is coming later.

We also have to make the distinctions of the fakes that have a working but very lightweight implementation. They might return hardcoded data unconditionally; always valid or always invalid data.

What is gMock?

Let's leave behind the theory now and talk about the gMock framework. gMock is one of the most widely used frameworks in C++. gMock comes in handy, when we cannot simply fake all the parameters and calls. It is useful when we need some mocks to be able to write better tests or to be able to write tests at all.

Though gMock has its own set of assertions, it's often used only for mocking and for the assertions gTest is used. I even saw gMock being combined with non-Google unit testing frameworks.

gMock promises a declarative, easy to learn and easy to use syntax for defining mocks, though in my experience people don't necessarily share this opinion.

gMock used to live in his own on Github project, but a couple of years ago it was merged into the gTest framework. There were also a couple of syntactical changes in v1.10. Unless I say so, in this series, you can assume that I'm using the syntax of the newer versions.

As the gMock for dummies mentions, there is a 3-step process to follow when you want to introduce a mock to your tests:

  • describe the interface to be mocked
  • create the mocks including all the expectations and behaviours
  • exercise the code that uses the mock objects

Let's go through the three steps. My goal in these articles is not to cover each and every possibility, but to explain the main ones and provide you with the sources to find the details.

Describe the interface to be mocked

In order to describe an interface, we have to use macros. While in general, it's good to avoid macros in your code, here you don't have any others options.

Taste the expression "mocking an interface". While in C++ there is no strong equivalent to Java's interface keyword and object-type, the closest thing is an abstract class with pure virtual functions.

class Car {
public:
  virtual ~Car() = default;
  virtual void startEngine() = 0;
  virtual int getTrunkSize() const = 0;
  virtual void addFuel(double quantity) = 0;
};
Enter fullscreen mode Exit fullscreen mode

The second closest thing is a class with some virtual functions in it:

class GPS {
public:
  virtual ~GPS() = default;
  virtual void addDestination(const std::string& destination) {}
  virtual Route getProposedRoute(int routeType) {}
};
Enter fullscreen mode Exit fullscreen mode

I wrote mocking an interface on purpose. It's much easier to mock a virtual function than a non-virtual one. (In this article I define interfaces using run-time polymorphism.)

Let's start first with the *virtual*s.

Mock a virtual function

Mocking a virtual function is easy in most cases, but there are a couple of things to pay attention to.

Let's start with mocking all the functions of the previously introduced Car class.

class MockCar : public Car {
public:
  MOCK_METHOD(void, startEngine, (), (override));
  MOCK_METHOD(int, getTrunkSize, (), (const, override));
  MOCK_METHOD(void, addFuel, (double quantity), (override));
};
Enter fullscreen mode Exit fullscreen mode

Let's break this down.

First, we create a class that inherits from the class we want to mock and prepend its name with Mock (the naming is just a convention).

Then in the public section, we start mocking the methods whose behaviour we want to change or monitor.

In earlier versions of gMock, there were a set of macros where the macro name included the number of function parameters and also the constness of the function, but since version 1.10.0, we can simply use the macro MOCK_METHOD.

Let's take the first example:

MOCK_METHOD(void, startEngine, (), (override));
Enter fullscreen mode Exit fullscreen mode

MOCK_METHOD takes the following parameters:

  • In the first position, we pass in the return type of the function, in this case, void.
  • The second parameter is the name of the function we want to mock.
  • The third parameter is the list of parameters the function takes. They should be listed surrounded by parentheses, which seems natural. You can basically copy-paste the parameter list from the function signature - just remove the parameter names.
  • The fourth and last parameter is a list (again surrounded by parentheses) of the qualifiers the function has. Ideally, all should be override as a mock function should mock the base class function. In addition, it takes the cv-qualifiers from the base class. Let's demonstrate it:
MOCK_METHOD(int, getTrunkSize, (), (const, override));
Enter fullscreen mode Exit fullscreen mode

But what does this macro do? Are we good yet?

No, we are not done yet. We should still provide a behaviour for the mocked methods. It doesn't matter whether a mocked function is defined in the base class or if it's abstract, MOCK_METHOD will provide an empty behaviour. The mocked function will do nothing and if the return type is not void, it will return the default constructed value.

If the return type has no default constructor and you don't provide a default action, gMock is going to throw an exception in the test body:

The mock function has no default action set, and its return type has no default value set."

But how do we provide the default action?

Stubs with gMock

As we discussed earlier, with gMock, we can create objects that are not only mocks, but stubs as well. And in fact, the way it's designed, stubs come first; a mocked function doesn't have a default behaviour, that's something we have to provide.

Describe, but don't assert

We can use the ON_CALL macro to provide behaviour.

For the ON_CALL macro, we have to pass in at the first place an instance on which the behaviour has to be defined and at the second place, we have to pass in the function name and all the expected parameters.

But how do we pass in the parameter list? We don't pass the types, but the exact values!

Let's take ON_CALL(c, addFuel(5.0)) as an example. This means that addFuel must be called with the value of 5.0 (implicit conversions are accepted), otherwise, the expectation will not be met.

If you don't know with what value addFuel should be called or if you don't care, you can use matchers!

Wildcards are often used, such as _: ON_CALL(c, addFuel(::testing::_)), but we can also express some more precise comparisons such as requiring that a parameter should be greater than a given value: ON_CALL(c, addFuel(::testing::Gt(5))).

You can find more information on these pre-defined matchers here.

After we set which function we provide with a behaviour, we have to set that action. We can do it with WillByDefault().

WillByDefault() can take many different parameters depending on what you want to achieve:

  • To return a value, you can use ::testing::Return(value), e.g. ON_CALL(c, getTrunkSize()).WillByDefault(::testing::Return(420))
  • To return a reference, you can use ::testing::ReturnRef(variable)
  • Return sets the value to be returned when you create the action, if you want to set the value when the action is executed, you can use ::testing::ReturnPointee(&vairable).

With ON_CALL, you have no other options to set the default behaviour than WillByDefault(). At the same time, you can use it after specifying different input parameters. This is completely valid:

ON_CALL(o, foo(1)).WillByDefault(::testing::Return(42))
ON_CALL(o, foo(2)).WillByDefault(::testing::Return(66))
Enter fullscreen mode Exit fullscreen mode

Describe and assert

ON_CALL only describes what a method should do when it's called, but it doesn't make sure that it gets called. If we need more than that, if need to assert that a method gets called, maybe even with a given set of parameters, we need to use another macro, EXPECT_CALL.

Just like ON_CALL, an EXPECT_CALL expression can grow long, but I think in most cases it remains simple. Let's start with what it takes as parameters.

EXPECT_CALL(c, getTrunkSize()) takes first the mocked object that it should watch and as a second one the method name, including its parameter list.

The parameters are passed the same way for EXPECT_CALL and ON_CALL.

EXPECT_CALL(c, addFuel(5.0)) means that addFuel must be called with the value of 5.0 (implicit conversions are still accepted), otherwise, the expectation will not be met.

Matchers can be used to widen the range of accepted values.

Wildcards are often used, such as _: EXPECT_CALL(c, addFuel(::testing::_)), but we can also express some more precise comparisons such as requiring that a parameter should be greater than a given value: EXPECT_CALL(c, addFuel(::testing::Gt(5))).

You can find more information on these pre-defined matchers here.

But this is only the first part of the EXPECT_CALL macro. You can chain it with different optional clauses.

The first is often referred to as cardinality and it's expressed with Times(n). n can be an exact number and in that case, if the given function is called more or fewer times - with the expected parameters - the test will fail.

We can also be less precise and write something like AtLeast(n) or AtMost(n), or even Between(n, m). You can find all the options for cardinality here.

EXPECT_CALL(c, addFuel(5.0)).Times(::testing::Between(1, 3)); would express that on instance c, addFuel with the parameter 5.0 should be called once, twice or even three times, but no more or fewer times.

As mentioned earlier, with mocks we can both observe how an object is used, but we can also define what it should do when it's called. We can define actions and we can do right after we set the cardinalities.

We have two options to define actions, we can use either WillOnce or WillRepeatedly. It's worth noting they can be also chained, WillOnce can be followed either by another WillOnce or WillRepeatedly.

These actions are self-evident, WillOnce will define the action to be taken for one call and WillRepeatedly for all the coming calls. What to pass them as a parameter?

  • To return a value, you can use ::testing::Return(value), e.g. EXPECT_CALL(c, getTrunkSize()).WillRepeatedly(::testing::Return(420))
  • To return a reference, you can use ::testing::ReturnRef(variable)
  • Return sets the value to be returned when you create the action, if you want to set the value when the action is executed, you can use ::testing::ReturnPointee(&vairable).

You saw in the previous example that I omitted to set the cardinalities - setting how many times we expect the function to be called. Setting the cardinalities is not mandatory and they can be deduced:

  • With no action set, it's inferred as Times(1)
  • If only WillOnce is used, it will be Times(n) where n is the number of times WillOnce is used
  • If both actions are used, it will be Times(AtLeast(n)) where n is the number of times WillOnce is used.

Differences between ON_CALL and EXPECT_CALL

As mentioned, the biggest difference between ON_CALL and EXPECT_CALL is that ON_CALL doesn't set any expectations.

It might sound counter-intuitive, but because of the above difference, you should use ON_CALL by default.

With EXPECT_CALL you might overspecify your tests and they become too brittle. You might couple the tests too closely to the implementation. Think about the problem of test contra-variance explained by Uncle Bob.

Use EXPECT_CALL only when the main purpose of a test is to make sure that something gets called, and even then you should think twice whether you want it to be tested at all.

What if you don't want to provide a default behaviour?

In the previous sections, we saw what happens when we have a mocked interface and we provide the mocked behaviour with either EXPECT_CALL or with ON_CALL. But what happens if we forget or we don't want to provide an overridden behaviour? You might think it's not realistic, but if you mock lots of functions of an API - it should probably be a red flag, by the way - it might happen that you don't want to provide a mocked behaviour every time for every function.

Even if you fail to provide a mocked behaviour, it will be automatically provided under certain conditions:

  • if the return type is void, the default action is a no-op. In other words, the mocked behaviour is to do nothing, instead of executing the original behaviour.
  • if the return type is not void, a default constructed value will be returned, given that the return type can be default constructed.

If the return type is not default constructible, you'll get a runtime exception:

unknown file: Failure
C++ exception with description "Uninteresting mock function call - returning default value.
Function call: getTrunkSize()
The mock function has no default action set, and its return type has no default value set."

If you don't get the runtime exception and the default action is used, you'll get a runtime warning from the gMock framework:

Uninteresting mock function call - returning default value.

It's quite straightforward, it doesn't require much explanation.

But how to get rid of it?

You have a couple of options:

  • You stop mocking this method.
  • You do provide a mocked behaviour.
  • Instead of simply creating an instance of your MockedClass, use ::testing::NiceMock<MockedClass> in order to silence such warnings. More about this next time.

But can we fall back to the original implementation?

Of course, we can do whatever we want! For this, we need a lambda:

ON_CALL(c, startEngine()).WillByDefault([&c](){return c.Car::startEngine();});
Enter fullscreen mode Exit fullscreen mode

As you can see, the lambda simply forwards the call to the underlying base class.

Conclusion

Today we started to discover one of the most popular mocking frameworks for C++, gMock. In this first episode, we saw how to mock virtual functions, how to provide simplified behaviour for them, and how to make assertions on how many times and with what inputs a mocked function is called.

Next time we'll see how to mock non-virtual members and free functions. Stay tuned.

Connect deeper

If you liked this article, please

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .