Home > Back-end >  How to add expectations alongside with ASSERT_DEATH in GoogleTest for C
How to add expectations alongside with ASSERT_DEATH in GoogleTest for C

Time:07-22

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

  • Related