krichards
krichards

Reputation: 41

How to pass std::function as an argument to a mocked method in gmock?

I am getting a runtime error when trying to pass an std:function as an argument to a mocked method inside EXPECT_CALL. I wish to verify that Bar is called with callback as an argument.

Code:

#include <gtest/gtest.h>
#include <gmock/gmock.h>

class Foo {
public:
    virtual void Bar(const std::function<void (const std::string &name)> &callback) = 0;
};

class MockFoo : public Foo {
public:
    MOCK_METHOD(void, Bar, (const std::function<void (const std::string &name)> &callback));
};

TEST(FooBarTest, callback) {
    MockFoo foo;

    const std::function<void(const std::string &name)> callback;

    EXPECT_CALL(foo, Bar(callback)).Times(1);

    foo.Bar(callback);
}

int main(int argc, char **argv) {
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

An error is produced at runtime:

/usr/local/include/gtest/gtest-matchers.h:211:60: error: no match for ‘operator==’ (operand types are ‘const std::function<void(const std::__cxx11::basic_string<char>&)>’ and ‘const std::function<void(const std::__cxx11::basic_string<char>&)>’)
   bool operator()(const A& a, const B& b) const { return a == b; }

Upvotes: 1

Views: 3356

Answers (2)

aschepler
aschepler

Reputation: 72271

The arguments to the method named in an EXPECT_CALL are actually matchers. When you just provide a value (something which is not already a gmock Matcher<T> type), that implies an Eq matcher. So EXPECT_CALL(foo, Bar(callback)) really means EXPECT_CALL(foo, Bar(Eq(callback))). The trouble is, std::function does not provide an operator== for comparing two functions. Its type-erasure properties mean an equality test would be impossible to implement in general, plus of course some of the class functor types it might wrap around won't have their own operator== either.

But it is possible to test whether a std::function contains a very specific object. If you don't want to just ignore the argument by expecting Bar(_), here's an idea for identifying whether a std::function is a specific dummy function.

First, create a callable class which we'll use to initialize the std::function object. I'll also have its operator() call a mock method, which is not needed when it's just passed to a mock function, but this will make it possible to use the same std::function in different contexts, or set expectations before one real function which will both call it directly and pass it to a mocked interface.

template <typename FuncT> class DummyFunction; // undefined

template <typename RetType, typename... ArgTypes>
class DummyFunction<RetType(ArgTypes...)> {
public:
    constexpr DummyFunction() : DummyFunction(0) {}
    explicit constexpr DummyFunction(int key) : m_key(key) {}
    constexpr DummyFunction(const DummyFunction& f) : m_key(f.m_key) {}
    constexpr int key() const { return m_key; }

    MOCK_METHOD(RetType, doCall, (ArgTypes...));

    RetType operator()(ArgTypes... args)
    { return doCall(std::forward<ArgTypes>(args)...); }

    friend constexpr bool operator==(const DummyFunction& f1, const DummyFunction& f2)
    { return f1.m_key == f2.m_key; }
    friend constexpr bool operator!=(const DummyFunction& f1, const DummyFunction& f2)
    { return !(f1 == f2); }
    friend std::ostream& operator<<(std::ostream& os, const DummyFunction& f)
    { return os << "DummyFunction(" << f.m_key << ")"; }

private:
    int m_key;
};

Then a gmock Matcher to test whether a std::function contains a DummyFunction object with the exact same function type as template parameter and the same key as a given DummyFunction object could look like this. Since it's possible to convert one type of std::function to another as long as the parameter types and return types convert correctly (or the return type changes to void), I made it a "polymorphic" matcher which accepts any std::function specialization for testing.

template <class DummyFuncType>
class IsDummyFunctionTester {
public:
    explicit constexpr IsDummyFunctionTester(int key) : m_key(key) {}

    // The three member functions required for gmock "PolymorphicMatcher":
    template <typename FuncType>
    bool MatchAndExplain(const std::function<FuncType>& f,
                         ::testing::MatchResultListener* listener) const {
        bool type_ok = f.target_type() == typeid(DummyFuncType);
        if (type_ok) {
            int f_key = f.template target<DummyFuncType>()->key();
            if (f_key == m_key) return true;
            *listener << "std::function contains DummyFunction(" << m_key << ")";
        } else if (!f) {
            *listener << "std::function is empty";
        } else {
            // Note name() is implementation dependent. For g++/clang it's mangled.
            *listener << "std::function target's type_info::name() is "
                      << f.target_type().name();
        }
        return false;
    }

    void DescribeTo(std::ostream* os) const
    { *os << "is a DummyFunction(" << m_key << ")"; }
    void DescribeNegationTo(std::ostream* os) const
    { *os << "is not a DummyFunction(" << m_key << ")"; }

private:
    int m_key;
};
template <typename FuncType>
decltype(auto) StdFuncIsDummyFunc(const DummyFunction<FuncType>& f) {
    return ::testing::MakePolymorphicMatcher(
        IsDummyFunctionTester<DummyFunction<FuncType>>(f.key()));
}

So finally, you can do:

TEST(FooBarTest, callback) {
    MockFoo foo;

    const DummyFunction<void(const std::string &name)> callback;

    EXPECT_CALL(foo, Bar(StdFuncIsDummyFunc(callback))).Times(1);

    foo.Bar(callback);
}

If you have just one DummyFunction, or if exactly which DummyFunction is which isn't important for the test, you can just use the default "key" of zero like above. Otherwise, you can specify unique keys for each distinct dummy callback.

Upvotes: 1

Quarra
Quarra

Reputation: 2695

There are pre-defined ACTIONS that you could use for this purpose. Your EXPECT_CALL would look something like:

using namespace testing; // for brevity

TEST(FooBarTest, callback) {
    MockFoo foo;

    const std::function<void(const std::string &name)> callback;

    EXPECT_CALL(foo, Bar(_)).WillOnce(Invoke(callback));

    foo.Bar(callback);
}

As pointed out by @IanGralinski, there's no matcher for std::function, so you can use any matcher for the call (i.e. _).

However, this is not how I would use gmock here - why mocking Foo if you use it directly? Usually mocks are used when you test interactions of your (real) class with other classes (mocked). So in your example: Foo could be mocked by MockFoo and used by some other class (real one), using dependency injection.

On a side note, remember to add virtual destructor to Foo if the object derived from Foo is to be deleted by pointer to Foo (this would be UB without virtual dtor).

Upvotes: 2

Related Questions