02 · Mocking with GoogleMock¶
A unit test should fail for one reason: the unit is wrong. That breaks down as soon as the unit talks to a database, a socket, or a sensor — now the test can fail because the network is slow, and it takes seconds instead of microseconds. GoogleMock replaces those collaborators with objects you control, and lets you assert on the interaction itself: was the upload attempted, with what payload, in what order.
1. What a mock actually is¶
A mock is a test double that records calls and verifies expectations. That last part is what separates it from a stub. A stub answers questions; a mock also insists it was asked.
Use a mock when the interaction is the behaviour under test — "on failure, the error is logged" is a statement about a call, not a return value. When you only need a canned answer, a stub or a fake is simpler and less brittle.
2. Declaring a mock¶
GoogleMock mocks a virtual interface. Define the seam first, then mock it.
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <string>
class Logger {
public:
virtual ~Logger() = default;
virtual void info(const std::string&) = 0;
virtual void error(const std::string&) = 0;
};
class Uploader {
public:
virtual ~Uploader() = default;
virtual bool put(const std::string& body) = 0;
};
class MockLogger : public Logger {
public:
MOCK_METHOD(void, info, (const std::string&), (override));
MOCK_METHOD(void, error, (const std::string&), (override));
};
class MockUploader : public Uploader {
public:
MOCK_METHOD(bool, put, (const std::string&), (override));
};
MOCK_METHOD(return_type, name, (args), (specs)). The specs list carries
override, const, noexcept, and Calling Convention. Always include
override — it is the only thing that catches a signature drift between the
interface and the mock.
Commas inside the argument list
MOCK_METHOD(void, put, (std::map<int, std::string> m), (override)) fails to
compile: the preprocessor splits on the comma inside map<int, string>.
Wrap the type in an extra pair of parentheses —
MOCK_METHOD(void, put, ((std::map<int, std::string>) m), (override)) — or
typedef it first. This is the single most common GoogleMock compile error.
3. The system under test¶
bool sync(Uploader& u, Logger& log, const std::string& body) {
log.info("start");
if (!u.put(body)) {
log.error("upload failed");
return false;
}
log.info("done");
return true;
}
Note that sync takes its collaborators as references. That is the seam — it is
what makes the function testable at all. A sync that constructed its own
HttpUploader internally could not be unit tested without a network.
4. Setting expectations¶
using ::testing::Return;
using ::testing::_;
using ::testing::NiceMock;
using ::testing::InSequence;
using ::testing::SaveArg;
using ::testing::DoAll;
TEST(Sync, LogsInOrderOnSuccess) {
NiceMock<MockLogger> log;
MockUploader up;
{
InSequence s; // order is asserted
EXPECT_CALL(log, info("start"));
EXPECT_CALL(up, put(_)).WillOnce(Return(true));
EXPECT_CALL(log, info("done"));
}
EXPECT_TRUE(sync(up, log, "payload"));
}
TEST(Sync, ReportsFailure) {
MockLogger log;
NiceMock<MockUploader> up;
EXPECT_CALL(up, put(_)).WillOnce(Return(false));
EXPECT_CALL(log, info("start"));
EXPECT_CALL(log, error("upload failed"));
EXPECT_FALSE(sync(up, log, "x"));
}
Build and run:
clang++ -std=c++17 -I/opt/homebrew/opt/googletest/include \
sync_test.cpp -L/opt/homebrew/opt/googletest/lib \
-lgtest -lgmock -lgtest_main -o sync_test
./sync_test
[==========] Running 3 tests from 1 test suite.
[----------] 3 tests from Sync
[ RUN ] Sync.LogsInOrderOnSuccess
[ OK ] Sync.LogsInOrderOnSuccess (0 ms)
[ RUN ] Sync.CapturesBodyArgument
[ OK ] Sync.CapturesBodyArgument (0 ms)
[ RUN ] Sync.ReportsFailure
[ OK ] Sync.ReportsFailure (0 ms)
[----------] 3 tests from Sync (0 ms total)
[==========] 3 tests from 1 test suite ran. (0 ms total)
[ PASSED ] 3 tests.
Expectations are verified when the mock is destroyed, not at the end of the statement. A missing call is reported as the test ends.
5. Cardinality and actions¶
EXPECT_CALL(m, read()) // implicitly Times(1) here
EXPECT_CALL(m, read()).Times(3);
EXPECT_CALL(m, read()).Times(::testing::AtLeast(1));
EXPECT_CALL(m, read()).Times(0); // must never be called
EXPECT_CALL(m, read())
.WillOnce(Return(10))
.WillOnce(Return(20))
.WillRepeatedly(Return(0)); // every call after the first two
EXPECT_CALL(m, put(_)).WillOnce([](const std::string& s) { return !s.empty(); });
EXPECT_CALL(m, load()).WillOnce(::testing::Throw(std::runtime_error("io")));
Implicit cardinality
If you supply WillOnce clauses and no Times, the cardinality becomes
exactly the number of WillOnce clauses. Adding a WillRepeatedly changes
it to "n or more". Writing .Times(2).WillOnce(Return(1)) means the second
call returns a default-constructed value, not 1 — a silent source of
zeros in your assertions.
6. ON_CALL vs EXPECT_CALL¶
ON_CALL sets behaviour without asserting anything. EXPECT_CALL sets
behaviour and demands the call happen.
ON_CALL(up, put(_)).WillByDefault(Return(true)); // background, no assertion
EXPECT_CALL(up, put("payload")); // this call must happen
The rule: state your default behaviour with ON_CALL in the fixture's SetUp,
and write EXPECT_CALL only for the one or two interactions the test is actually
about. A test with eight EXPECT_CALLs is asserting on the implementation, and
will break on every refactor.
7. Nice, naggy and strict¶
An "uninteresting call" is a call to a mock method with no matching
EXPECT_CALL. The default mock is naggy: it allows the call but prints a
warning. This is why real suites are noisy.
MockLogger log; // naggy — warns on uninteresting calls
NiceMock<MockLogger> log; // nice — silent
StrictMock<MockLogger> log; // strict — uninteresting call fails the test
Use NiceMock for collaborators that aren't the subject of the test (a logger
you don't care about), and StrictMock sparingly, where any unexpected traffic
is genuinely a bug. Defaulting everything to StrictMock produces a suite that
fails whenever anyone adds a log line.
8. Capturing arguments¶
When you need to inspect a complex argument rather than match it, capture it.
TEST(Sync, CapturesBodyArgument) {
NiceMock<MockLogger> log;
NiceMock<MockUploader> up;
std::string seen;
EXPECT_CALL(up, put(_))
.WillOnce(DoAll(SaveArg<0>(&seen), Return(true)));
sync(up, log, "hello-world");
EXPECT_EQ(seen, "hello-world");
}
SaveArg<N> stores argument N; DoAll chains it with the return action.
Capturing then asserting is usually clearer than a hand-written matcher, and the
failure message shows the actual value.
9. Matchers on arguments¶
using ::testing::HasSubstr, ::testing::Field, ::testing::Property;
using ::testing::AllOf, ::testing::Not, ::testing::Truly;
EXPECT_CALL(up, put(HasSubstr("\"id\":")));
EXPECT_CALL(sink, write(Field(&Packet::length, 64)));
EXPECT_CALL(sink, write(Property(&Packet::valid, true)));
EXPECT_CALL(up, put(AllOf(Not(HasSubstr("password")), HasSubstr("user"))));
EXPECT_CALL(up, put(Truly([](const std::string& s){ return s.size() < 1024; })));
10. Reference¶
| Need | Write |
|---|---|
| Any argument | EXPECT_CALL(m, f(_)) |
| Specific argument | EXPECT_CALL(m, f("abc")) |
| Argument predicate | EXPECT_CALL(m, f(Truly(pred))) |
| Field of a struct argument | Field(&S::member, matcher) |
| Return a value | .WillOnce(Return(v)) |
| Return different values in turn | .WillOnce(Return(a)).WillOnce(Return(b)) |
| Throw | .WillOnce(Throw(std::runtime_error("x"))) |
| Capture an argument | .WillOnce(DoAll(SaveArg<0>(&dest), Return(v))) |
| Default behaviour, no assertion | ON_CALL(m, f(_)).WillByDefault(...) |
| Must never be called | EXPECT_CALL(m, f(_)).Times(0) |
| Enforce call order | { InSequence s; ... } |
| Silence uninteresting calls | NiceMock<M> |
| Fail on uninteresting calls | StrictMock<M> |
| Verify early | Mock::VerifyAndClearExpectations(&m) |
How It Actually Works: how MOCK_METHOD intercepts a call via the vtable¶
Mocking a C++ interface works because virtual dispatch is already a runtime indirection — GoogleMock just puts its own function at the other end of it.
- A virtual call is a pointer lookup, not a direct call. Any object with
virtual methods carries a hidden vtable pointer as its first member. Every
obj->VirtualMethod(args)compiles to: load the vtable pointer fromobj, load the function pointer at that method's fixed slot index in the vtable, then call through that pointer. The compiler decided the slot index at compile time from the class declaration, but the address stored at that slot is decided at object-construction time. That's the entire seam mocking exploits. MOCK_METHOD(R, Name, (Args...), (overrides))generates an override that points that vtable slot at GoogleMock's machinery instead of your logic. The macro expands into a realName(Args...) overridemethod whose body callsGMOCK_MOCKER_(...)->Invoke(...). Because it's declaredoverrideon a method whose interface base class declared itvirtual, constructing aMockFooobject naturally writes GoogleMock'sNameinto the same vtable slot the realFoowould have used — no linker tricks, no monkey-patching, just ordinary C++ dynamic dispatch pointed at generated code.- This is exactly why GoogleMock can only mock virtual methods (or, with
more setup, template-parameterized seams) and not arbitrary free functions
or non-virtual methods — a non-virtual call is resolved statically at
compile time by name, with no indirection to intercept. It's the same
reason Level 1's CMocka module needed a manual function-pointer or
#ifdefseam for C: C has no vtable to hijack, so the seam has to be built by hand instead of being a free side effect of the language's dispatch mechanism. EXPECT_CALLbuilds an expectation object thatInvoke()consults at call time. EachEXPECT_CALLregisters a{matcher, cardinality, actions}tuple on the mock. When the intercepted call fires, GoogleMock'sInvoke()walks the mock's registered expectations (in an order that depends onInSequenceblocks), finds the first unsatisfied match, applies itsWillOnce/WillRepeatedlyaction to compute a return value, and increments that expectation's call count — which is also the data structureTimes()cardinality checks are validated against when the mock is destroyed orVerifyAndClearExpectationsruns. An "uninteresting call" warning fires precisely whenInvoke()finds no matching expectation at all.
Exercise¶
Build a small BackupService and test it entirely with mocks.
class Clock { public: virtual ~Clock() = default;
virtual long now() = 0; };
class Storage { public: virtual ~Storage() = default;
virtual bool write(const std::string& key,
const std::string& data) = 0;
virtual bool remove(const std::string& key) = 0; };
class Notifier { public: virtual ~Notifier() = default;
virtual void notify(const std::string& msg) = 0; };
// Writes data under key "backup-<now>". On write failure, notifies once and
// returns false. On success, removes any key passed in `supersedes` (if
// non-empty) and returns true.
bool run_backup(Clock&, Storage&, Notifier&,
const std::string& data, const std::string& supersedes);
-
Implement
run_backupto that contract, then mock all three collaborators. -
Write the success test. Use
InSequenceto assertnow()is called beforewrite(), andwrite()beforeremove(). UseSaveArg<0>to capture the key and assert it matchesbackup-plus the timestamp your mock clock returned. -
Write the failure test. Make
writereturnfalseand assertnotifyis called exactly once andremoveis never called (Times(0)). Confirm the test fails if you delete theTimes(0)guard and change the implementation to always remove. -
Exercise the empty-
supersedesboundary. Assertremoveis not called whensupersedesis empty. -
Feel the three strictness modes. Add a stray
notify("debug")call to your implementation. Run the suite withMockNotifier, thenNiceMock<MockNotifier>, thenStrictMock<MockNotifier>. Record what each prints, and write two sentences on which one you'd want in CI and why. -
Refactor away over-specification. Move the "always succeed" behaviour of
Storage::writeinto anON_CALLin a fixtureSetUp(), and delete everyEXPECT_CALLthat isn't the point of its test. Count how many expectations you removed.