Search code examples
c++googletestgooglemock

google mock delegate to fake must be copy constructable


Using google test and mocks it seems that I cannot delegate a call from a mock to a fake if the function returns a reference to a data object. The google test version I'm using is 1.10.0 from the released zip.

In the code below when I delegate from a mock to a fake I get an error indicating that the copy ctor is deleted. Yes, it must be deleted for this code to work properly.

Is there any way to delegate mocks to fakes with gmock for functions which return references to classes?

Note that in the code below, there is a macro: #define USE_MOCK_ACCESSOR 1 This is used to test the desired path of test code execution. Defining this value to zero merely tests the AccessorImpl class for correct behavior. I did this to check that I was not somehow malforming the classes and instances within this class. Thanks for your input.

#include "gtest/gtest.h"
#include "gmock/gmock.h"

#include <cstdint>

using ::testing::Invoke;
using ::testing::Mock;
using ::testing::Return;
using ::testing::ReturnRef;
using ::testing::_;

class Accessor
{
public:
    virtual ~Accessor()                  = default;
    Accessor()                           = default;
    Accessor(Accessor const&)            = delete;
    Accessor(Accessor&&)                 = delete;
    Accessor& operator=(Accessor const&) = delete;
    Accessor& operator=(Accessor&&)      = delete;

    struct Foo
    {
        ~Foo()                     = default;
        Foo()                      = default;
        Foo(Foo const&)            = delete;
        Foo(Foo&&)                 = delete;
        Foo& operator=(Foo const&) = delete;
        Foo& operator=(Foo&&)      = delete;

        uint32_t thing_1 = 13u;
    };

    struct Bar
    {
        ~Bar()                     = default;
        Bar()                      = default;
        Bar(Bar const&)            = delete;
        Bar(Bar&&)                 = delete;
        Bar& operator=(Bar const&) = delete;
        Bar& operator=(Bar&&)      = delete;

        uint32_t thing_2 = 79u;
    };

    virtual Foo&       GetFoo()       = 0;
    virtual Bar const& GetBar() const = 0;
};

class AccessorImpl: public Accessor
{
public:
    ~AccessorImpl() override                       = default;
    AccessorImpl()                                 = default;
    AccessorImpl(AccessorImpl const& ) = delete;
    AccessorImpl(AccessorImpl&&)                   = delete;
    AccessorImpl& operator=(AccessorImpl const&)   = delete;
    AccessorImpl& operator=(AccessorImpl&&)        = delete;

    Foo&       GetFoo()       override { return this->foo_; };
    Bar const& GetBar() const override { return this->bar_; };

private:
    Foo foo_;
    Bar bar_;
};

#define USE_MOCK_ACCESSOR 1
#if USE_MOCK_ACCESSOR
class MockAccessor : public Accessor
{
public:
    MOCK_METHOD0(GetFoo, Foo&());
    MOCK_CONST_METHOD0(GetBar, Bar&());
};

class MockAccessorWithFake : public MockAccessor
{
public:
    MockAccessorWithFake() : MockAccessor(), fake_accessor_()
    {
        ON_CALL(*this, GetFoo).WillByDefault([this]() {
            return this->fake_accessor_.GetFoo();
        });

        ON_CALL(*this, GetBar).WillByDefault([this]() {
            return this->fake_accessor_.GetBar();
        });
    }

private:
    AccessorImpl fake_accessor_;
};
#endif

TEST(AccessorTest, test)
{
#if USE_MOCK_ACCESSOR
    MockAccessorWithFake accessor;
#else
    AccessorImpl accessor;
#endif
    EXPECT_EQ(accessor.GetFoo().thing_1, 13u);
    EXPECT_EQ(accessor.GetBar().thing_2, 79u);
}

Errors from clang compiler:

test_accessor.cc:83:20: error: call to deleted constructor of 'Accessor::Foo'
            return this->fake_accessor_.GetFoo();
                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~

test_accessor.cc:26:9: note: 'Foo' has been explicitly marked deleted here
        Foo(Foo const&)            = delete;
        ^

test_accessor.cc:82:46: error: no viable conversion from '(lambda at
      test_accessor.cc:82:46)' to 'const Action<Accessor::Foo &()>'
        ON_CALL(*this, GetFoo).WillByDefault([this]() {
                                             ^~~~~~~~~~

googletest-src/googlemock/include/gmock/gmock-actions.h:339:7: note: candidate constructor (the implicit copy constructor) not viable:
      no known conversion from '(lambda at test_accessor.cc:82:46)' to 'const testing::Action<Accessor::Foo &()> &' for 1st argument
class Action {
      ^

googletest-src/googlemock/include/gmock/gmock-actions.h:339:7: note: candidate constructor (the implicit move constructor) not viable:
      no known conversion from '(lambda at test_accessor.cc:82:46)' to 'testing::Action<Accessor::Foo &()> &&' for 1st argument

googletest-src/googlemock/include/gmock/gmock-actions.h:367:3: note: candidate template ignored: requirement
      '::std::is_constructible<std::__1::function<Accessor::Foo &()>, (lambda at test_accessor.cc:82:46)>::value' was not satisfied
      [with G = (lambda at test_accessor.cc:82:46)]
  Action(G&& fun) : fun_(::std::forward<G>(fun)) {}  // NOLINT
  ^

googletest-src/googlemock/include/gmock/gmock-spec-builders.h:323:46: note: passing argument to parameter 'action' here
  OnCallSpec& WillByDefault(const Action<F>& action) {

Solution

  • Problem 1

    The rules for auto type deduction strip references. Therefore, the return type of your lambda is deduced to be Foo instead of Foo& which then requires a copy. If you want to return a reference from a lambda, you have to specify this explicitly using the trailing return type syntax, either by explicitly setting the return type to Foo&, by using auto& to force a reference type deduction, or by using decltype(auto), which preserves references. See link, link, link, in the last link the relevant part is: "If P is a reference type, the type referred to by P is used for deduction."

    [this]() {return this->fake_accessor_.GetFoo();}          // Returns Foo
    [this]() -> Foo& {return this->fake_accessor_.GetFoo();}  // Returns Foo&
    
    [this]() -> auto {return this->fake_accessor_.GetFoo();}  // Returns Foo
    [this]() -> auto& {return this->fake_accessor_.GetFoo();} // Returns Foo&
    
    [this]() -> decltype(auto) {return this->fake_accessor_.GetFoo();} // Returns Foo&
    

    So you should change the lambdas you pass to ON_CALL to return a reference type, e.g.:

            ON_CALL(*this, GetFoo).WillByDefault([this]() -> Foo& {
                return this->fake_accessor_.GetFoo();
            });
    
            ON_CALL(*this, GetBar).WillByDefault([this]() -> Bar const& {
                return this->fake_accessor_.GetBar();
            });
    

    Without this, you get the errors:

    test.cpp:83:20: error: call to deleted constructor of 'Accessor::Foo'
                return this->fake_accessor_.GetFoo();
                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    test.cpp:26:9: note: 'Foo' has been explicitly marked deleted here
            Foo(Foo const&)            = delete;
            ^
    test.cpp:82:46: error: no viable conversion from '(lambda at test.cpp:82:46)' to 'const Action<Accessor::Foo &()>'
            ON_CALL(*this, GetFoo).WillByDefault([this]() {
                                                 ^~~~~~~~~~
    

    Problem 2

    In your declaration of GetBar, you have two usages of const:

    1. The function is a const member function (meaning it cannot modify the state of this).
    2. The return value is a reference to a const Bar.

    The MOCK_CONST_METHOD0 macro only declares const member function. To cover the const in the return value, your mock should be:

      MOCK_CONST_METHOD0(GetBar, Bar const&());
    

    Without this change, the following error will be generated:

    test.cpp:86:46: error: no viable conversion from '(lambda at test.cpp:86:46)' to 'const Action<Accessor::Bar &()>'
            ON_CALL(*this, GetBar).WillByDefault([this]() -> Bar const& {
                                                 ^~~~~~~~~~~~~~~~~~~~~~~~
    /usr/include/gmock/gmock-actions.h:357:7: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from '(lambda at test.cpp:86:46)' to 'const testing::Action<Accessor::Bar &()> &' for 1st argument
    class Action {