How to forward unnamed function arguments

Viewed 113

So, I'm trying to create a mocking-macro for my virtual class functions.

This is a example of a mocked class with one mock function. (This works as expected)

class MockEnvironment : public IEnvironment {
public:
    using runType = int(std::string program,
                        std::vector<std::string> arguments);

    MockedFunction<runType> runMock = MockedFunction<runType>("run");

    int run(std::string program, std::vector<std::string> arguments) override {
        return runMock(program, arguments);
    }

    //    MOCK_METHOD(int, run, (std::string, std::vector<std::string>)); // this is the goal
};

MockedFunction is a class with a operator() that is supposed to be called whenever the function is called.

My goal is to wrap the function in a macro, to be able to mock any function. The problem as you might see is that when generalizing, i would need to handle a function with unnamed arguments.


#define MOCK_METHOD(ret, name, args)                                           \
    MockedFunction<ret(args)> mocked_##name =                                  \
        MockedFunction<ret(args)>(#name);                                      \
    ret name##Mock(args) { /* how to solve? */ }

// This would expand to something like
MockedFunction<...> runMock = MockedFunction<...>("run"); // More detailed in above example

int run(std::string, std::vector<std::string>) override {
    return runMock(/*how to forward the arguments when they do not have names?*/)
}

I have thought of using some kind of varadic template, but I haven't found any way to make it work. I have also tried to read googles mock-implementation, but i find it hard to see how they have solved it.

So my question is simply. How do i move the content of the arguments in run to the function to be called?

It is ok for me to use up to c++17. I do realize that it would be easier if you specify in the macro how many arguments you need, but it would be optimal if it would be possible to solve for the more general example.

Edit: This is the best i have come up with if i know the number of arguments in beforehand.

#define INTERNAL_MOCK_METHOD_COMMON(ret, name, args)                           \
    using name##T = ret(args);                                                 \
    unittest::MockedFunction<ret(args)> mock_##name =                          \
        unittest::MockedFunction<ret(args)>(#name);

#define MOCK_METHOD(ret, name)                                                 \
    INTERNAL_MOCK_METHOD_COMMON(ret, name, ())                                 \
    ret name() {                                                               \
        return mock_##name();                                                  \
    }

#define MOCK_METHOD1(ret, name, args)                                          \
    INTERNAL_MOCK_METHOD_COMMON(ret, name, args)                               \
    ret name(typename unittest::MockedFunction<name##T>::ArgT<0>::type a) {    \
        return mock_##name(a);                                                 \
    }

#define MOCK_METHOD2(ret, name, args)                                          \
    INTERNAL_MOCK_METHOD_COMMON(ret, name, args)                               \
    ret name(typename unittest::MockedFunction<name##T>::ArgT<0>::type a,      \
             typename unittest::MockedFunction<name##T>::ArgT<1>::type b) {    \
        return mock_##name(a, b);                                              \
    }

// ...

template <typename T>
class MockedFunction {};

template <typename ReturnT, typename... ArgsT>
class MockedFunction<ReturnT(ArgsT...)> {
public:
    // Return argument type at the specified index
    template <int i>
    struct ArgT {
        using type = typename std::tuple_element_t<i, std::tuple<ArgsT...>>;
    };

Then it is possible to use like this


class IObject {
public:
    virtual int update() = 0;
    virtual void setValue(int) = 0;
};

class MockObject : public IObject {
public:
    MOCK_METHOD(int, update);
    MOCK_METHOD1(void, setValue, (int));
};

To be clear: It would be nice to not have to specify the numbers of arguments with numbers.

2 Answers

You cannot use unnamed arguments. You might consider naming them, perhaps like this:

#define MOCK_METHOD(ret, name, args, names)                                           \
    MockedFunction<ret(args)> mocked_##name =                                  \
        MockedFunction<ret(args)>(#name);                                      \
    ret name##Mock(args) { return runMock names; }

MOCK_METHOD(int, run, (std::string s, std::vector<std::string> v), (s, v));
#define identity(...) __VA_ARGS__

#define MOCK_METHOD(ret, name, params, args)                                           \
    MockedFunction<ret identity(params)> mocked_##name =                                  \
        MockedFunction<ret identity(params)>(#name);                                      \
    ret name##Mock(identity params) { return name args; }

is this what you want? it's allowed to name the parameters in a declaration of function type.

Related