Search code examples
unit-testingfunction-pointersgooglemock

googletest gmock: testing a C style callback function


Need a suggestion in testing C style callback function with gooogletest gmock.

It's easy to test a callback defined with std::function. These 2 topics helped a lot:

  1. Using google test to check callbacks
  2. How to use gmock to mock up a std::function?

But unfortunately my environment can't use std:: namespace (Arduino AVR platform). So if I change from std::function to C style callback like: typedef void (*CallbackFunction)(void *); then it doesn't compile, and I get the following error: error: cannot convert ‘std::function<void(void*)>’ to ‘Foo::CallbackFunction’ {aka ‘void (*)(void*)’}

Any help appreciated.

Please see the code below:

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

using ::testing::_;
using ::testing::MockFunction;
using ::testing::Return;

class Foo
{
public:
    using CallbackFunction = std::function<void(void *)>;   //* works just fine
    // typedef void (*CallbackFunction)(void *);            //! doesn't work

    void SetCallback(CallbackFunction callback)
    {
        _callback = callback;
    }

    void InvokeCallback()
    {
        _callback(nullptr);
    }

private:
    CallbackFunction _callback;
};

class FooTest : public testing::Test
{
public:
    using CallbackFunctionMock = testing::MockFunction<void(void *)>;
    CallbackFunctionMock callbackFunctionMock;
};

TEST_F(FooTest, simple)
{
    EXPECT_CALL(callbackFunctionMock, Call(_)).Times(1);

    Foo foo;
    foo.SetCallback(callbackFunctionMock.AsStdFunction());
    foo.InvokeCallback();
}

Solution

  • The compiler is right - there's no way to convert the object of std::function to a function pointer in a way you want to use it (or I don't know such trick, maybe someone here can show me otherwise).

    You can mock this method by hand using global state. This is kinda ugly, but it gets the work done. I'd switch to std::function whenever possible, but as long as you have to deal with raw ptrs, this can help you out:

    #include <gtest/gtest.h>
    #include <gmock/gmock.h>
    
    using ::testing::_;
    using ::testing::MockFunction;
    using ::testing::Return;
    
    class Foo
    {
    public:
        // using CallbackFunction = std::function<void(void *)>;   //* works just fine
        typedef void (*CallbackFunction)(void *);            //! doesn't work
    
        void SetCallback(CallbackFunction callback)
        {
            _callback = callback;
        }
    
        void InvokeCallback()
        {
            _callback(nullptr);
        }
    
        void InvokeCallback(int* int_ptr)
        {
            
            _callback(int_ptr);
        }
    
    private:
        CallbackFunction _callback;
    };
    
    // global state - should be kept in test.cpp
    namespace {
    int callback_call_counter = 0;
    static void* callback_arg = nullptr;
    }
    
    void CallbackFunctionMock(void* arg) {
        ++callback_call_counter;
        callback_arg = arg;
    }
    
    class FooTest : public testing::Test
    {
    public:
        // FooTest ctor is called before each FooTest test case, so resetting the global variables
        // to the correct state can be handled here 
        FooTest() {
            callback_call_counter = 0;
            callback_arg = nullptr;
        }
    };
    
    TEST_F(FooTest, simple)
    {
    
        Foo foo;
        foo.SetCallback(CallbackFunctionMock);
        foo.InvokeCallback();
    
        ASSERT_EQ(1U, callback_call_counter);
        ASSERT_THAT(callback_arg, testing::IsNull());
    }
    
    TEST_F(FooTest, simple_int)
    {
        int i = 42;
        Foo foo;
        foo.SetCallback(CallbackFunctionMock);
        foo.InvokeCallback(&i);
    
        ASSERT_EQ(1U, callback_call_counter);
        ASSERT_THAT(callback_arg, testing::Not(testing::IsNull()));
        ASSERT_EQ(callback_arg, &i);
        ASSERT_EQ(*static_cast<int*>(callback_arg), 42);
    }