Just for fun, I’m trying to create a class, that allows “basic mocking functionality”.
More concretely, you can set return values for function calls.
That’s it ;D
I have a solution for that, however I don’t really like it.
I’ll show the code, give an explanation and tell what I don’t like about it.
I am using up to C++20.
#include <any>
#include <iostream>
#include <string_view>
#include <typeindex>
#include <unordered_map>
using namespace std;
class MockHelper {
public:
virtual ~MockHelper() = default;
template<typename Class, typename Ret, typename ...Args>
void
setFunctionReturnValue(const string_view &functionName, Ret(Class::*functionPtr)(Args...), const Ret &returnValue) {
functionsReturnValues[functionName][type_index(typeid(functionPtr))] = make_any<Ret>(returnValue);
}
protected:
template<typename Class, typename Ret, typename ...Args>
Ret handleCall(const string_view &functionName, Ret(Class::*functionPtr)(Args...)) {
const auto &returnValues = functionsReturnValues[functionName];
const auto typeIndex = type_index(typeid(functionPtr));
if (!returnValues.contains(typeIndex))
return {};
return any_cast<Ret>(returnValues.at(typeIndex));
}
private:
unordered_map<string_view, unordered_map<type_index, any>> functionsReturnValues;
};
class SomethingToMock : public MockHelper {
public:
int f() {
return handleCall("f", &SomethingToMock::f);
}
int g() {
// g is overloaded, thus we need to provide the template arguments explicitly
return handleCall<SomethingToMock, int>("g", &SomethingToMock::g);
}
int g(bool) {
return handleCall<SomethingToMock, int, bool>("g", &SomethingToMock::g);
}
};
int main() {
SomethingToMock beingMocked;
// Setup expected return values
beingMocked.setFunctionReturnValue("f", &SomethingToMock::f, 0);
// again, g is overloaded, thus we need to provide the template arguments explicitly
beingMocked.setFunctionReturnValue<SomethingToMock, int>("g", &SomethingToMock::g, 1);
beingMocked.setFunctionReturnValue<SomethingToMock, int, bool>("g", &SomethingToMock::g, 2);
// Show that this is actually working
std::cout << "Should be 0 and is actually " << beingMocked.f() << std::endl;
std::cout << "Should be 1 and is actually " << beingMocked.g() << std::endl;
std::cout << "Should be 2 and is actually " << beingMocked.g(true) << std::endl;
return 0;
}
class MockHelper
allows to set return values for function calls (via setFunctionReturnValue
), and allows to execute those function calls from derived classes (via handleCall
).
class SomethingToMock
derives from class MockHelper
and contains 3 member functions which I use to show stuff.
In the main
function we setup return values and show that it actually works.
A LIVE DEMO.
I dislike, that I have to give functions names as strings to all relevant functions. This is because if I wouldn’t I couldn’t distinguish the functions.
E.g.
type_index(typeid(&SomethingToMock::f)) == type_index(typeid(static_cast<int (SomethingToMock::*)()>(&SomethingToMock::g)))
evaluates to true
, which means f()
and g()
cannot be distinguished by the signatures only, we have to include the name as well.
So, my question is, can we somehow generate unique keys for all the functions, so that they can be used in e.g. an unordered_map, without having to explicitly give the names of the functions?
So, as an example the main
function should look more like this:
int main() {
SomethingToMock beingMocked;
// Setup expected return values
beingMocked.setFunctionReturnValue(&SomethingToMock::f, 0);
// again, g is overloaded, thus we need to provide the template arguments explicitly
beingMocked.setFunctionReturnValue<SomethingToMock, int>(&SomethingToMock::g, 1);
beingMocked.setFunctionReturnValue<SomethingToMock, int, bool>(&SomethingToMock::g, 2);
// Show that this is actually working
std::cout << "Should be 0 and is actually " << beingMocked.f() << std::endl;
std::cout << "Should be 1 and is actually " << beingMocked.g() << std::endl;
std::cout << "Should be 2 and is actually " << beingMocked.g(true) << std::endl;
return 0;
}
MockingMocker is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.