Mockaron

Philippe Daouadi

<philippe@tanker.io>

May 18th, 2017

An example class, and tests

A simple class

struct GetAndCache {
  NetworkManager _networkManager;
  int _cachedValue{-1};

  int getValue() {
    if (_cachedValue == -1)
      _cachedValue = std::stoi(
          _networkManager.httpGet("http://get.id/"));
    return _cachedValue;
  }
};

Unit tests

GetAndCache g;
CHECK(?? == g.getValue());
  • What should we expect?
  • A unit test will call a production service?
  • What if network is unavailable?
  • How to test how the class behaves when there is no network?

We must simulate NetworkManager's behavior.

Dependency Injection

struct GetAndCache {
  NetworkManager& _networkManager;

  GetAndCache(NetworkManager& networkManager)
    : _networkManager(networkManager) {}

  // ...
};

Now we can give the real NetworkManager in the application, and a fake one in the tests, maybe...

Mocking

FakeNetworkManager nm;
GetAndCache g(nm); // there must be some magic here

{
  EXPECT_CALL(nm, httpGet("http://get.id/"))
    .WillReturn("42");
  CHECK(42 == g.getValue());
}
  • If httpGet isn't called, the test fails.
  • If httpGet is called twice, the test fails.

Failure case

FakeNetworkManager nm;
GetAndCache g(nm); // there must be some magic here

{
  EXPECT_CALL(nm, httpGet("http://get.id/"))
    .WillThrow(NoNetworkError{});
  CHECK_THROWS_AS(g.getValue(), NoNetworkError);
}

A couple mocking libraries

googlemock:

  • Old but does the job
  • C++03 or so (no support for move-only types)

trompeloeil:

  • Pretty much the same as googlemock
  • C++14

How should I inject my mock?

The problem

We need to pass a FakeNetworkManager instead of a NetworkManager.

First solution

image

Inheritance

struct NetworkManagerInterface {
  virtual std::string httpGet(std::string const& url) = 0;
};

struct GetAndCache {
  NetworkManagerInterface& _networkManager;

  GetAndCache(NetworkManagerInterface& networkManager)
    : _networkManager(networkManager) {}

  // ...
};

Inheritance – Why not?

  • Lots of boilerplate (duplicated) code
  • Less performance
  • Clumsy code: there is an interface but only one implementation
  • Very Java

Ugly inheritance

struct NetworkManager {
  MOCKABLE std::string httpGet(std::string const& url);
};

// for test code
#define MOCKABLE virtual

// for production code (actually not mandatory)
#define MOCKABLE

Ugly inheritance – Why not?

  • You need to compile all your code twice, once for tests, once for production (or make everything always virtual)
  • You can't avoid calling the constructor/destructor
struct NetworkManager {
  NetworkManager(SocketManager&);
  ~NetworkManager();
};
struct FakeNetworkManager : NetworkManager {
  FakeNetworkManager()
    : NetworkManager(/*I don't have a SocketManager!*/) {}
};

Mockaron

The solution

Macros.

Hooking the real call

std::string
NetworkManager::httpGet(std::string const& url) {
  // give the class name, the method name,
  //   and all its arguments to mockaron
  // this will intercept the call
  MOCKARON_HOOK(NetworkManager, httpGet, url);

  // real implementation of the method
  return curl_do_magic_request(_curlObj, url.c_str());
}

Defining a mock

struct FakeNetworkManager : mockaron::mock_impl {
  FakeNetworkManager() {
    MOCKARON_DEFINE_IMPL(NetworkManager, httpGet);
  }

  std::string httpGet(std::string const& url) {
    return "42";
  }
  // or use googlemock/trompeloeil
};

Using the mock

mockaron::mock<NetworkManager, FakeNetworkManager>
  fakeNetworkManager;

// get returns a NetworkManager&
GetAndCache g(fakeNetworkManager.get());

// BOOM, hooked.
CHECK("42" == fakeNetworkManager.get().httpGet("myurl"));
CHECK(42 == g.getValue());

With googlemock

mockaron::mock<NetworkManager, FakeNetworkManager>
  fakeNetworkManager;

// get returns a NetworkManager&
GetAndCache g(fakeNetworkManager.get());

{
  // get_mock_impl returns a FakeNetworkManager&
  EXPECT_CALL(fakeNetworkManager.get_mock_impl(),
      httpGet("http://get.id/"))
    .WillReturn("42");
  CHECK(42 == g.getValue());
}

Pros

  • Minimal modification of your code
  • Hooks can be removed in production builds
  • Easy to integrate with googlemock, trompeloeil or anything
  • Also supports free functions
  • Tested on GCC 5.3, clang 3.8 to 4.0, MSVC 2015 update 2 and 3
  • It works for meâ„¢

Cons

  • Needs C++14
  • You still need to change your library code
  • Undefined behavior/crash if you forget to hook a method
  • Relies on undefined behavior even in normal execution

Disclaimer

This was not my idea.

https://blogs.unity3d.com/2015/11/25/mocking-faking-and-stubbing-c/

Under the hood

MOCKARON_HOOK

MOCKARON_HOOK(NetworkManager, httpGet, url);

// is expanded to this (simplified code)

if (mockaron::is_a_mock(this))
  return reinterpret_cast<mockaron::mock*>(this)
    ->call("httpGet", url);

MOCKARON_DEFINE_IMPL

MOCKARON_DEFINE_IMPL(NetworkManager, httpGet);

// is expanded to this (simplified code)

_methods["httpGet"] = [this](auto&& url) {
  return this->httpGet(std::forward<decltype(url)>(url));
};

mockaron::mock<T, U>::get

fakeNetworkManager.get();

// is implemented like this

NetworkManager&
mock<NetworkManager, FakeNetworkManager>::get() {
  return *reinterpret_cast<NetworkManager*>(this);
}

The end

Get it

https://github.com/TankerApp/mockaron

Thank you

image

Questions?

License

Copyright © 2017 - Tanker

CC BY 4.0