struct GetAndCache {
NetworkManager _networkManager;
int _cachedValue{-1};
int getValue() {
if (_cachedValue == -1)
_cachedValue = std::stoi(
_networkManager.httpGet("http://get.id/"));
return _cachedValue;
}
};
GetAndCache g;
CHECK(?? == g.getValue());
We must simulate NetworkManager's behavior.
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...
FakeNetworkManager nm;
GetAndCache g(nm); // there must be some magic here
{
EXPECT_CALL(nm, httpGet("http://get.id/"))
.WillReturn("42");
CHECK(42 == g.getValue());
}
httpGet
isn't called, the test fails.httpGet
is called twice, the test fails.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);
}
googlemock:
trompeloeil:
We need to pass a FakeNetworkManager instead of a NetworkManager.
struct NetworkManagerInterface {
virtual std::string httpGet(std::string const& url) = 0;
};
struct GetAndCache {
NetworkManagerInterface& _networkManager;
GetAndCache(NetworkManagerInterface& networkManager)
: _networkManager(networkManager) {}
// ...
};
struct NetworkManager {
MOCKABLE std::string httpGet(std::string const& url);
};
// for test code
#define MOCKABLE virtual
// for production code (actually not mandatory)
#define MOCKABLE
struct NetworkManager {
NetworkManager(SocketManager&);
~NetworkManager();
};
struct FakeNetworkManager : NetworkManager {
FakeNetworkManager()
: NetworkManager(/*I don't have a SocketManager!*/) {}
};
Macros.
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());
}
struct FakeNetworkManager : mockaron::mock_impl {
FakeNetworkManager() {
MOCKARON_DEFINE_IMPL(NetworkManager, httpGet);
}
std::string httpGet(std::string const& url) {
return "42";
}
// or use googlemock/trompeloeil
};
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());
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());
}
This was not my idea.
https://blogs.unity3d.com/2015/11/25/mocking-faking-and-stubbing-c/
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(NetworkManager, httpGet);
// is expanded to this (simplified code)
_methods["httpGet"] = [this](auto&& url) {
return this->httpGet(std::forward<decltype(url)>(url));
};
fakeNetworkManager.get();
// is implemented like this
NetworkManager&
mock<NetworkManager, FakeNetworkManager>::get() {
return *reinterpret_cast<NetworkManager*>(this);
}
Questions?
Copyright © 2017 - Tanker