Given those interfaces:
class ITemperature
{
public:
virtual ~ITemperature() = deafult;
virtual int get_temp() const = 0;
};
class IHumidity
{
public:
virtual ~IHumidity() = deafult;
virtual int get_humidity() const = 0;
};
And this SUT:
class SoftwareUnderTest
{
public:
SoftwareUnderTest(std::unique_ptr<ITemperature> p_temp,
std::unique_ptr<IHumidity> p_humidity)
: m_temp{std::move(p_temp)}, m_humidity{std::move(p_humidity)}
{}
bool checker()
{
assert(m_temp && "No temperature!");
if (m_temp->get_temp() < 50)
{
return true;
}
assert(m_humidity && "No humidity");
if (m_humidity->get_humidity() < 50)
{
return true;
}
return false;
}
private:
std::unique_ptr<ITemperature> m_temp;
std::unique_ptr<IHumidity> m_humidity;
};
And this mocks:
class MockITemperature : public ITemperature
{
public:
MOCK_METHOD(int, get_temp, (), (const override));
};
class MockIHumidity : public IHumidity
{
public:
MOCK_METHOD(int, get_humidity, (), (const override));
};
I want to make a test that checks that get_temp
is called and also that the second assert (the one that checks that the humidity is nullptr
), but when a do this test, I get the assert, but I the expectation tells me that it's never called (but it is actually called once)
this is the test:
class Fixture : pu`blic testing::Test
{
protected:
void SetUp() override
{
m_sut = std::make_unique<SoftwareUnderTest>(m_mock_temperature, m_mock_humidity);
}
std::unique_ptr<StrickMockOf<MockITemperature>> m_mock_temperature = std::make_shared<StrickMockOf<MockITemperature>>();
std::unique_ptr<StrickMockOf<MockIHumidity>> m_mock_humidity;
std::unique_ptr<SoftwareUnderTest> m_sut;
};
TEST_F(Fixture, GIVEN_AnInvalidHumidityInjection_THEN_TestMustDie)
{
EXPECT_CALL(*m_mock_temperature, get_temp).Times(1);
ASSERT_DEATH(m_sut->checker(), "No humidity");
}
CodePudding user response:
It seems to be due to some tricky mechanism that is used in googletest to assert death (they mention creating a child process). I did not find a way to fix it correctly, but I found one (not so great) workaround:
SoftwareUnderTest(ITemperature* p_temp, IHumidity* p_humidity) // changed signature to allow leaks, I guess you cannot really do it in the production
and then:
class Fixture : public testing::Test
{
public:
Fixture(): m_mock_temperature(new MockITemperature), m_mock_humidity(nullptr) {}
~Fixture() {
// Mock::VerifyAndClearExpectations(m_mock_temperature); // if I uncomment that, for some reason the test will fail anyway
std::cout << "Dtor" << std::endl;
// delete m_mock_temperature; // if I delete the mock correctly, the test will fail
}
protected:
void SetUp() override
{
// m_sut.reset(new SoftwareUnderTest(m_mock_temperature.get(), m_mock_humidity.get()));
m_sut.reset(new SoftwareUnderTest(m_mock_temperature, m_mock_humidity));
}
// std::unique_ptr<MockITemperature> m_mock_temperature; // if I use smart pointers, the test will fail
// std::unique_ptr<MockIHumidity> m_mock_humidity;
MockITemperature* m_mock_temperature;
MockIHumidity* m_mock_humidity;
std::unique_ptr<SoftwareUnderTest> m_sut;
};
TEST_F(Fixture, GIVEN_AnInvalidHumidityInjection_THEN_TestMustDie)
{
EXPECT_CALL(*m_mock_temperature, get_temp).Times(1).WillOnce(Return(60)); // this is to ensure to go over first check, seems you forgot
ASSERT_DEATH(m_sut->checker(), "No humidity");
std::cout << "after checks" << std::endl;
}
Sorry, that's all I could figure out for the moment. Maybe you can submit a new issue in gtest github while waiting for a better answer.
CodePudding user response:
Apparently, this is a known limitation, see here and here.
From what I have managed to discover by experimentation so far:
If you can live with the error message about leaking mocks (haven't checked if it's true or a false positive, suppressing it by AllowLeak triggers the actual crash), it can be done by making the mocks outlive the test suite and then wrapping references/pointers to them in one more interface implementation.
//mocks and SUT as they were
namespace
{
std::unique_ptr<testing::StrictMock<MockIHumidity>> mock_humidity;
std::unique_ptr<testing::StrictMock<MockITemperature>> mock_temperature;
}
struct MockITemperatureWrapper : MockITemperature
{
MockITemperatureWrapper(MockITemperature* ptr_) : ptr{ptr_} {assert(ptr);}
int get_temp() const override { return ptr->get_temp(); }
MockITemperature* ptr;
};
struct Fixture : testing::Test
{
void SetUp() override
{
mock_temperature
= std::make_unique<testing::StrictMock<MockITemperature>>();
m_mock_temperature = mock_temperature.get();
// testing::Mock::AllowLeak(m_mock_temperature);
m_sut = std::make_unique<SoftwareUnderTest>(
std::make_unique<MockITemperatureWrapper>(m_mock_temperature), nullptr);
}
testing::StrictMock<MockITemperature>* m_mock_temperature;
std::unique_ptr<SoftwareUnderTest> m_sut;
};
TEST_F(Fixture, GIVEN_AnInvalidHumidityInjection_THEN_TestMustDie)
{
EXPECT_CALL(*m_mock_temperature, get_temp).WillOnce(testing::Return(60));
ASSERT_DEATH(m_sut->checker(), "No humidity");
}
https://godbolt.org/z/vKnP7TsrW
Another option would be passing a lambda containing the whole to ASSERT_DEATH:
TEST_F(Fixture, GIVEN_AnInvalidHumidityInjection_THEN_TestMustDie)
{
ASSERT_DEATH(
[this] {
EXPECT_CALL(*m_mock_temperature, get_temp)
.WillOnce(testing::Return(60));
m_sut->checker();
}(), "No humidity");
}
Works, but looks ugly, see here.
Last but not least: one can use custom assert or replace__assert_failed
function and throw from it (possibly some custom exception), then use ASSERT_THROW instead of ASSERT_DEATH. While I'm not sure replacing __assert_failed
is legal standard-wise (probably not), it works in practice:
struct AssertFailed : std::runtime_error
{
using runtime_error::runtime_error;
};
void __assert_fail(
const char* expr,
const char *filename,
unsigned int line,
const char *assert_func )
{
std::stringstream conv;
conv << expr << ' ' << filename << ' ' << line << ' ' << assert_func;
throw AssertFailed(conv.str());
}
Example: https://godbolt.org/z/Tszv6Echj