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;
