Home > front end >  How to use gmock SaveArgPointee with with std::shared_ptr of derived class
How to use gmock SaveArgPointee with with std::shared_ptr of derived class

Time:12-01

I have a BaseMessage class from which I derive several different DerivedMessage subclasses and want to send them like this:

class BaseMessage {
public:
   virtual std::vector<uint8_t> data() const noexcept = 0;
   virtual ~BaseMessage() = default;
   [...]
}

class DerivedMessage : public BaseMessage {
public:
   [...]
   std::vector<uint8_t> data() const noexcept override { return m_data; }
private:
   std::vector<uint8_t> m_data;
}

// simplified 
class Tcp {
public
   virtual void sendMessage(std::shared_ptr<BaseMessage> msg) { write(msg->data());}
   [...]
};

class SomeClass {
public:
   SomeClass(Tcp& tcp) : m_tcp(tcp) {}
   void writeDataToRemote(std::shared_ptr<DerivedMessage> derived) const {
      m_tcp.sendMessage(derived);
private:
   Tcp m_tcp;
}
};

Now I want to write tests for SomeClass with gtest.

Therefore I mock the function of the TCP class:

class MockTcp : public Tcp {
MOCK_METHOD(void, sendMessage, (std::shared_ptr<ralco::CommandMsg> msg), (override));
[...]
}

Let's assume that all is simplified up to here but works.

So in the test, I'd like to inspect the argument given to sendMessage in the function writeDataToRemote. I read about ::testing::SaveArg and ::testing::SaveArgPointee on StackOverflow (but not in the documentation, though).

TEST(SomeClassTest, writesMessageToSocket){
   MockTcp mockTcp;
   SomeClass sc(mockTcp);
   
   // >>>how to declare msgArg here?<<<
   EXPECT_CALL(mockTcp, sendMessage(_)).Times(1).WillOnce(::testing::SaveArgPointee<0>(msgArg));
   const auto derivedMsg = std::make_shared<DerivedMessage>();
   sc.writeDataToRemote(derivedMsg);
   // further inspection of msgArg follows
}

As written in the code comment, I don't know how to declare the msgArg variable so that it can be assigned the actual argument given to sendMessage. When using SaveArg I think I'd get a dangling pointer and doing it as above I get errors because the message cannot be copy-assigned. Any hints apreciated.

CodePudding user response:

In your case you actually want to just save and inspect the considered shared_ptr, so it is enough to use SaveArg:

    MockTcp mockTcp;
    SomeClass sc(mockTcp);
    std::shared_ptr<BaseMessage> bm;
    EXPECT_CALL(mockTcp, sendMessage(_)).Times(1).WillOnce(::testing::SaveArg<0>(&bm));  // it will effectively do bm = arg;
    const auto derivedMsg = std::make_shared<DerivedMessage>();
    sc.writeDataToRemote(derivedMsg);
    // verify that the argument that you captured is indeed pointing to the same message
    std::cout << derivedMsg.get() << std::endl;
    std::cout << bm.get() << std::endl;

The common misunderstanding of SaveArgPointee is that it assigns value pointed by the arg to your local variable in test, which in your case maybe is not a good idea, because it would invoke a copy constructor of Message.

Alternatively I can recommend using Invoke. It is very generic and easy to use. You can e.g. capture the desired argument like that:

    MockTcp mockTcp;
    SomeClass sc(mockTcp);
    std::shared_ptr<BaseMessage> bm;
    EXPECT_CALL(mockTcp, sendMessage(_)).Times(1).WillOnce(::testing::Invoke([&bm](auto arg) { bm = arg; }));
    const auto derivedMsg2 = std::make_shared<DerivedMessage>();
    sc.writeDataToRemote(derivedMsg2);
    std::cout << derivedMsg2.get() << std::endl;
    std::cout << bm.get() << std::endl;

Or using a raw pointer to BaseMessage:

    BaseMessage* rawBm = nullptr;
    EXPECT_CALL(mockTcp, sendMessage(_)).Times(1).WillOnce(Invoke([&rawBm](auto arg) { rawBm = arg.get(); }));
    const auto derivedMsg2 = std::make_shared<DerivedMessage>();
    sc.writeDataToRemote(derivedMsg2);
    std::cout << derivedMsg2.get() << std::endl;
    std::cout << rawBm << std::endl;
  • Related