I have two classes, Server
and Client
, each with their own mock class and has a getClients
and getServer
method respectively (the actual classes are much bigger, but this is the minimal case).
I want to add a variable in the MockServer
that's returned by the getClients
method, allowing me to modify this variable in my tests whenever appropriate (note the clients
variable).
While this works, it will cause the two mock objects to keep each other alive, each keeping a shared_ptr
reference to the other, causing the test to leak objects.
Please, look at the example provided below.
Question: How can I modify the Mock Classes, without modifying the interfaces or the create
functions, to prevent leaked mock objects while preserving the clients
variable?
#include <gmock/gmock.h>
#include <gtest/gtest.h>
struct Client;
struct Server
{
virtual const std::vector<std::shared_ptr<Client>>& getClients() = 0;
};
struct Client
{
virtual std::shared_ptr<Server> getServer() = 0;
};
struct MockServer : virtual Server
{
MOCK_METHOD0(getClients, const std::vector<std::shared_ptr<Client>>&());
std::vector<std::shared_ptr<Client>> clients; // I want to add this variable...
protected:
MockServer()
{
ON_CALL(*this, getClients()).WillByDefault(testing::ReturnRef(this->clients)); // ...and return it whenever getClients is called.
}
~MockServer()
{
clients.clear(); // This doesn't help...
std::cout << __func__ << " - This is never printed." << std::endl;
}
public:
static std::shared_ptr<MockServer> create()
{
return std::make_shared<testing::NiceMock<MockServer>>();
}
};
struct MockClient : virtual Client
{
MOCK_METHOD0(getServer, std::shared_ptr<Server>());
protected:
MockClient(std::shared_ptr<Server> server)
{
ON_CALL(*this, getServer()).WillByDefault(testing::Return(server));
}
~MockClient()
{
std::cout << __func__ << " - This is never printed." << std::endl;
}
public:
static std::shared_ptr<MockClient> create(std::shared_ptr<Server> server)
{
return std::make_shared<testing::NiceMock<MockClient>>(server);
}
};
TEST(ExampleOfLeak, testLeakedMockObjects)
{
auto server = MockServer::create();
auto client1 = MockClient::create(server);
auto client2 = MockClient::create(server);
EXPECT_EQ(server, client1->getServer());
EXPECT_EQ(server, client2->getServer());
EXPECT_TRUE(server->getClients().empty());
server->clients.push_back(client1); // This causes leaked mock objects!
server->clients.push_back(client2); // And this too...
EXPECT_EQ(client1, server->getClients().front());
EXPECT_EQ(client2, server->getClients().back());
// server->clients.clear(); // This prevents mock objects from leaking, but doing this manually everywhere is highly unfeasible.
}
Result:
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from ExampleOfLeak
[ RUN ] ExampleOfLeak.testLeakedMockObjects
[ OK ] ExampleOfLeak.testLeakedMockObjects (0 ms)
[----------] 1 test from ExampleOfLeak (0 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (0 ms total)
[ PASSED ] 1 test.
foo.cc:43: ERROR: this mock object (used in test ExampleOfLeak.testLeakedMockObjects) should be deleted but never is. Its address is @0x2fc3170.
foo.cc:43: ERROR: this mock object (used in test ExampleOfLeak.testLeakedMockObjects) should be deleted but never is. Its address is @0x2fcbcb0.
foo.cc:22: ERROR: this mock object (used in test ExampleOfLeak.testLeakedMockObjects) should be deleted but never is. Its address is @0x2fd6570.
ERROR: 3 leaked mock objects found at program exit.
It's possible to convert a weak_ptr
into a shared_ptr
using the lock()
method.
This way you can store a weak_ptr
in you mock class and later convert it.
Though do be careful and make sure the weak_ptr
hasn't expired()
.
#include <gmock/gmock.h>
#include <gtest/gtest.h>
struct Client;
struct Server
{
virtual const std::vector<std::shared_ptr<Client>>& getClients() = 0;
};
struct Client
{
virtual std::shared_ptr<Server> getServer() = 0;
};
struct MockServer : virtual Server
{
MOCK_METHOD0(getClients, const std::vector<std::shared_ptr<Client>>&());
std::vector<std::shared_ptr<Client>> clients;
protected:
MockServer()
{
ON_CALL(*this, getClients()).WillByDefault(testing::ReturnRef(this->clients));
}
~MockServer()
{
clients.clear();
std::cout << __func__ << " - This is printed." << std::endl;
}
public:
static std::shared_ptr<MockServer> create()
{
return std::make_shared<testing::NiceMock<MockServer>>();
}
};
ACTION_P(ReturnWeakToShared, wp)
{
bool isWeakPtrExpired = wp.expired();
EXPECT_FALSE(isWeakPtrExpired) << "Dangling pointer.";
return wp.lock();
}
struct MockClient : virtual Client
{
MOCK_METHOD0(getServer, std::shared_ptr<Server>());
std::weak_ptr<Server> server;
protected:
MockClient(std::shared_ptr<Server> server)
{
this->server = server;
ON_CALL(*this, getServer()).WillByDefault(ReturnWeakToShared(this->server));
}
~MockClient()
{
std::cout << __func__ << " - This is printed." << std::endl;
}
public:
static std::shared_ptr<MockClient> create(std::shared_ptr<Server> server)
{
return std::make_shared<testing::NiceMock<MockClient>>(server);
}
};
TEST(ExampleOfLeak, testLeakedMockObjects)
{
auto server = MockServer::create();
auto client1 = MockClient::create(server);
auto client2 = MockClient::create(server);
EXPECT_EQ(server, client1->getServer());
EXPECT_EQ(server, client2->getServer());
EXPECT_TRUE(server->getClients().empty());
server->clients.push_back(client1);
server->clients.push_back(client2);
EXPECT_EQ(client1, server->getClients().front());
EXPECT_EQ(client2, server->getClients().back());
}