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;