Mocking dependencies in C++ for testing.

Mock the Boundary, Never the Thing You Are Testing

I spent six years in high-frequency trading environments where a single millisecond of jitter was a catastrophe, and if there is one thing I learned, it’s that most “best practices” for mocking dependencies in C++ are actually just clever ways to hide architectural rot. I’ve sat through countless design reviews where developers suggested wrapping every single low-level component in a virtual interface just to satisfy a testing requirement. They think they’re being “testable,” but all they’re doing is introducing indirection overhead and bloating the vtable for no reason other than to make a mediocre test suite run. It’s a lazy way to avoid dealing with the actual coupling in your system.

I’m not here to teach you how to follow a textbook or how to write boilerplate that satisfies a linter. I want to show you how to design for testability without sacrificing the performance or the mechanical sympathy that C++ is built for. We are going to look at template-based dependency injection, the reality of compile-time polymorphism, and how to avoid the trap of over-mocking. I’ll show you the rules that actually matter when you’re trying to verify logic without turning your codebase into a labyrinth of pointers.

Table of Contents

Stubbing vs Mocking in C Where the Logic Breaks

Stubbing vs Mocking in C Where the Logic Breaks

Most developers use these terms interchangeably, which is a mistake that leads to brittle test suites. When I talk about stubbing, I’m talking about providing a canned answer to a call—a way to feed your function the data it needs to keep moving. It’s about state. You’re essentially using abstract base classes for testing to bypass a real database or a network socket just to reach a specific branch in your logic. A stub doesn’t care how many times it was called; it just exists to satisfy the compiler and the immediate requirements of the execution path.

Mocking is a different beast entirely. It’s about behavior. When you use a framework like GoogleMock, you aren’t just providing data; you are setting expectations. You are telling the test, “This function must be called exactly twice, with these specific arguments, in this specific order.” If the code deviates, the test fails. This is where decoupling code for testability becomes a high-wire act. If you over-mock, you end up testing the implementation details rather than the outcome, creating a suite that breaks every time you refactor a single line of private logic.

Decoupling Code for Testability Without Losing Control

Decoupling Code for Testability Without Losing Control

The problem isn’t just about writing tests; it’s about the architectural tax you pay to make them possible. Most developers fall into the trap of hardcoding concrete types deep within their business logic, effectively welding their components together. To fix this, you have to embrace decoupling code for testability, which usually means moving away from direct instantiation and toward a more flexible ownership model.

In practice, this often involves using abstract base classes for testing. By defining a narrow interface for your service—say, a `DataProvider`—your high-level logic no longer cares whether it’s talking to a high-frequency market data feed or a simple in-memory array. This is the essence of dependency injection in C++. You aren’t just making the code easier to test; you’re making it more modular and less prone to the kind of rigid coupling that makes refactoring a nightmare.

However, there is a fine line between clean abstraction and over-engineering. If you find yourself creating an interface for every single class in your system, you’re just adding indirection for the sake of it. The goal is to identify the true boundaries where external state or side effects enter your system, and that is exactly where you apply your mocks.

Five Rules for When Your Mocks Start Fighting Your Architecture

  • Stop using templates for everything just because you want “zero-cost” abstractions. If you use template-based policy injection for every single dependency, your compile times will bloat, and your error messages will become unreadable nightmares. Use virtual interfaces unless you are in a tight, latency-critical loop where the vtable lookup actually matters.
  • Beware the “God Mock.” If your mock object requires fifty lines of setup just to test a single branch of logic, your class has too many responsibilities. A mock should be a surgical tool, not a replacement for a poorly designed subsystem.
  • Don’t mock what you don’t own. If you’re trying to mock a third-party library’s internal class, you’re playing a losing game. Wrap the external dependency in your own thin abstraction layer, then mock that wrapper. It keeps your tests decoupled from the vendor’s API changes.
  • Watch out for side effects in your mocks. A mock that silently changes global state or modifies a singleton is just a bug in disguise. If your test passes because the mock hid a memory leak or a race condition, you haven’t written a test; you’ve written a lie.
  • Prefer state verification over interaction testing where possible. It is tempting to verify that `func()` was called exactly three times with specific arguments, but that makes your tests brittle. If you can just check that the resulting object state is correct, do that instead. It allows you to refactor the internal implementation without breaking every single test case.

The Cost of Blind Testing

Stop treating stubs and mocks as interchangeable; using a stub when you need to verify behavior is just testing that your code can run, not that it actually works.

Don’t let your abstractions become a lie; if your mock doesn’t respect the same constraints as your real production dependency, your green tests are providing nothing but a false sense of security.

Testability isn’t free—every interface you add to facilitate mocking is a new architectural decision that affects your binary’s layout and your compiler’s ability to inline.

The Cost of Doing It Wrong

At the end of the day, mocking isn’t about following a textbook pattern; it’s about managing the boundary between your logic and the messy reality of the outside world. If you rely solely on stubs, you’re only checking if your code can survive a specific input. If you use mocks poorly, you end up testing the implementation details rather than the behavior, creating a suite of tests that break every time you refactor a single line. You have to decide where the abstraction boundary actually lies. If you don’t draw that line clearly, you’ll find yourself fighting your own test suite more often than you’re actually finding bugs.

Don’t let the complexity of template-heavy mocking frameworks or the overhead of virtual dispatch scare you away from writing testable code. The goal isn’t to achieve 100% coverage for the sake of a metric; it’s to build a system where you can actually trust your assertions when the clock is ticking. C++ gives you the tools to be precise, so use them. Write your interfaces with the intent to decouple, and treat your mocks as a way to prove your assumptions are correct. That is how you move from just shipping code to shipping reliable systems.

About Ruaridh Kensington-Oyelaran

C++ rewards people who know what the compiler is allowed to do. I write about the rules that bite, the ones nobody mentions until you have already shipped the bug.

More From Author

Immutable data and concurrency in software engineering.

Data Nobody Writes Needs No Synchronisation

Templates for generic containers in binary code.

One Template Becomes Many Functions in the Binary