13

I have a class Foo that uses class Bar. Bar is used only in Foo and Foo is managing Bar, therefore I use unique_ptr (not a reference, because I don't need Bar outside of Foo):

using namespace std;
struct IBar {
    virtual ~IBar() = default;  
    virtual void DoSth() = 0;
};

struct Bar : public IBar {
    void DoSth() override { cout <<"Bar is doing sth" << endl;};    
};

struct Foo {
  Foo(unique_ptr<IBar> bar) : bar_(std::move(bar)) {}

  void DoIt() {
    bar_->DoSth();
  }
private:
  unique_ptr<IBar> bar_;
};

So far so good, this works fine. However, I have a problem when I want to unit test the code:

namespace {
struct BarMock : public IBar {
  MOCK_METHOD0(DoSth, void());
};
}

struct FooTest : public Test {
  FooTest() : barMock{ make_unique<BarMock>() }, out(std::move(barMock)) {}

  unique_ptr<BarMock> barMock;
  Foo out;
};

TEST_F(FooTest, shouldDoItWhenDoSth) {
  EXPECT_CALL(*barMock, DoSth());

  out.DoIt();
}

The test fails because the mock object was transfered fo Foo, and setting an expectation on such mock fails.

Possible options of DI:

  • by shared_ptr: is too much in this case (Bar object is not shared between Foo any anything else)
  • by reference to IBar: isn't an option (Bar is not stored outside Foo, so the Bar object created would be destructed leaving Foo with dangling reference)
  • by unique_ptr: isn't testable in the presented way
  • by passing by value: isn't possible (copying will occure - same issue as with unique_ptr).

The only solution I got is to store raw pointer to BarMock before Foo become solely owner of BarMock, i.e.:

struct FooTest : public Test {
  FooTest() : barMock{new BarMock} {
    auto ptr = unique_ptr<BarMock>(barMock);
    out.reset(new Foo(std::move(ptr)));
  }

  BarMock* barMock;
  unique_ptr<Foo> out;
};

Isn't there a cleaner solution? Do I have to use static dependency injection (templates)?

273K
  • 29,503
  • 10
  • 41
  • 64
Quarra
  • 2,527
  • 1
  • 19
  • 27
  • 2
    You may be interested in reading [this answer](http://stackoverflow.com/questions/7616475/can-google-mock-a-method-with-a-smart-pointer-return-type/11548191#11548191). – πάντα ῥεῖ Nov 09 '16 at 13:32
  • @πάντα ῥεῖ: thank you for the link. I've already seen it and it works for methods that take unique_ptr as a parameter - but I'm not sure one could apply this approach for constructors. – Quarra Nov 09 '16 at 14:01

3 Answers3

4

Not something I would recommend in production environment actually, but aliasing constructor of shared_ptr represents maybe a dirty and working solution for your case.
A minimal, working example (that doesn't use gtest, sorry, I'm from mobile app and can't test it directly):

#include<memory>
#include<iostream>
#include<utility>

struct IBar {
    virtual ~IBar() = default;  
    virtual void DoSth() = 0;
};

struct Bar : public IBar {
    void DoSth() override { std::cout <<"Bar is doing sth" << std::endl;};    
};

struct Foo {
    Foo(std::unique_ptr<IBar> bar) : bar(std::move(bar)) {}

    void DoIt() {
        bar->DoSth();
    }
private:
    std::unique_ptr<IBar> bar;
};

int main() {
    std::unique_ptr<Bar> bar = std::make_unique<Bar>();
    std::shared_ptr<Bar> shared{std::shared_ptr<Bar>{}, bar.get()};
    Foo foo{std::move(bar)};
    shared->DoSth();
    foo.DoIt();
}

I guess your test would become something like this:

struct BarMock: public IBar {
    MOCK_METHOD0(DoSth, void());
};

struct FooTest : public testing::Test {
    FooTest() {
        std::unique_ptr<BarMock> bar = std::make_unique<BarMock>();
        barMock = std::shared_ptr<BarMock>{std::shared_ptr<BarMock>{}, bar.get()};
        out = std::make_unique<Foo>{std::move(bar)};
    }

    std::shared_ptr<BarMock> barMock;
    std::unique_ptr<Foo> out;
};

TEST_F(FooTest, shouldDoItWhenDoSth) {
    EXPECT_CALL(*barMock, DoSth());
    out->DoIt();
}

What does the aliasing constructor do?

template< class Y > 
shared_ptr( const shared_ptr<Y>& r, element_type *ptr );

The aliasing constructor: constructs a shared_ptr which shares ownership information with r, but holds an unrelated and unmanaged pointer ptr. Even if this shared_ptr is the last of the group to go out of scope, it will call the destructor for the object originally managed by r. However, calling get() on this will always return a copy of ptr. It is the responsibility of the programmer to make sure that this ptr remains valid as long as this shared_ptr exists, such as in the typical use cases where ptr is a member of the object managed by r or is an alias (e.g., downcast) of r.get()

malchemist
  • 434
  • 2
  • 13
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • 4
    +1 for aliasing constructor of shared_ptr, good to know. If I understand it correctly, the shared_ptr created with aliasing constructor is not managing the ptr passed, so it works as a regular raw pointer. Or is there any advantage in using it instead of a raw ptr? – Quarra Nov 10 '16 at 10:26
2

You can keep a reference to the mocked object before passing it to the constructor. I think it makes the code a tad bit brittle, due to member initialization ordering, but it is clearer semantically what it means. Ownership of BarMock still belongs solely to Foo, with a reference handle kept by FooTest (similar to this answer).

Basically the same as your answer, but using reference instead of a raw pointer

class FooTest : public ::testing::Test
{
    protected:
        FooTest() :
            bar_mock_ptr(std::make_unique<BarMock>()),
            bar_mock(*bar_mock_ptr),
            foo(std::move(bar_mock_ptr))
        {}
    private:
        // This must be declared before bar_mock due to how member initialization is ordered
        std::unique_ptr<BarMock> bar_mock_ptr; // moved and should not be used anymore
    protected:
        BarMock& bar_mock;
        Foo foo; //ensure foo has the same lifetime as bar_mock
}
Rufus
  • 5,111
  • 4
  • 28
  • 45
  • This doesn't solve the problem in the production code: whenever `Foo` is created, `IBar` reference needs to be passed to it - meaning that whoever manages `Foo`, needs to manage `IBar` while I want to have `IBar` managed by `Foo` so that the user of `Foo` can just inject the dependency, but doesn't have to manage the lifetime of it. – Quarra Nov 13 '20 at 06:33
  • I'm not changing the definition of `Foo`. It is still taking and keeping a unique pointer to `IBar` (i.e. managing `IBar`). I'm just keeping a reference to `IBar` before passing it to `Foo` for testing/mocking sake – Rufus Nov 15 '20 at 04:52
  • you're right - my bad, I haven't noticed. Nice approach. – Quarra Nov 16 '20 at 07:28
1

After all, I ended up using this approach everywhere:

struct FooTest : public Test {
  FooTest() : barMock{new BarMock} {
    auto ptr = unique_ptr<BarMock>(barMock);
    out.reset(new Foo(std::move(ptr)));
  }

  BarMock* barMock;
  unique_ptr<Foo> out;
};

and it works fine with gtest/gmock.

Quarra
  • 2,527
  • 1
  • 19
  • 27