slinkin
slinkin

Reputation: 405

EXPECT_CALL isn't satisfied, but test is passed

I'm trying to test object behavior, but my test always passes even if the mock method hasn't been called.

The case is simple. There are few io-blocks. Each block does data processing and pass them to the next block. The code is presented bellow.

GtestCheck.h

#include <memory>
#include <string>

#include <gtest/gtest.h>
#include <gmock/gmock.h>

using ::testing::NiceMock;
using ::testing::_;
using ::testing::NotNull;
using ::testing::Exactly;

using namespace std::placeholders;

class IoBlock;
typedef std::shared_ptr<IoBlock> IoBlockSharedPtr;

enum class ModuleType : uint8_t
{
    None        =   0x00,
    Type0       =   0x01,
    Type1       =   0x02,
};

class IoBlock
{
    public:
        IoBlock() = delete;
        IoBlock(const IoBlock&) = delete;
        IoBlock(const IoBlock&&) = delete;
        virtual ~IoBlock();

    public:
        IoBlock(const std::string& config);

    public:
        void SetOutputObject(IoBlockSharedPtr outputModule);
        virtual void Input(int x, int y, int z) = 0;

    protected:
        void Output(int x, int y, int z);

    protected:
        ModuleType _moduleType = ModuleType::None;
        std::string _config = "";
        IoBlockSharedPtr _outputBlockPtr;
};


class IoType0 : public IoBlock
{
    public:
        IoType0() = delete;
        IoType0(const IoType0&) = delete;
        IoType0(const IoType0&&) = delete;
        virtual ~IoType0() override;

    public:
        IoType0(const std::string& config) : IoBlock(config) { }

    public:
        virtual void Input(int x, int y, int z) override final;
};


class IoType1mock : public IoBlock
{
    public:
        IoType1mock() = delete;
        IoType1mock(const IoType1mock&) = delete;
        IoType1mock(const IoType1mock&&) = delete;
        virtual ~IoType1mock() override;

    public:
        IoType1mock(const std::string& config);

    public:
        MOCK_METHOD(void, Input, (int x, int y, int z), (override, final));
};

class IoBlockTest : public ::testing::Test
{
    protected:
        void SetUp() override
        {
            _configString = "none0";
            _ioType0block = std::make_shared<IoType0>(_configString);
            _ioType1block = std::make_shared<NiceMock<IoType1mock>>(_configString);

            _ioType1block->SetOutputObject(_ioType0block);
            _ioType0block->SetOutputObject(_ioType1block);

            // gtest reports shared_ptr memory leak
            ::testing::Mock::AllowLeak(_ioType1block.get());
        }

        virtual void TearDown() override { }

        static void SetUpTestCase() { }

        static void TearDownTestCase() { }

    public:
        std::string _configString = "none";
        std::shared_ptr<IoType0> _ioType0block = nullptr;
        std::shared_ptr<NiceMock<IoType1mock>> _ioType1block = nullptr;
};

GtestCheck.cpp

#include "GtestCheck.h"

IoBlock::IoBlock(const std::string& config)
    : _config(config) {}

void IoBlock::SetOutputObject(IoBlockSharedPtr outputModule)
{
    _outputBlockPtr = outputModule;
}

void IoBlock::Output(int x, int y, int z)
{
    if ( _outputBlockPtr != nullptr )
        _outputBlockPtr->Input(x, y, z);
}

IoBlock::~IoBlock() {}

IoType0::~IoType0() {}

IoType1mock::~IoType1mock() {}

void IoType0::Input(int x, int y, int z)
{
    static size_t callCounter = 0;

    if (callCounter++ == 5)
        Output(x, y, z);
}

IoType1mock::IoType1mock(const std::string& config)
    : IoBlock(config) { }

main.cpp

#include <iostream>

#include "GtestCheck.h"

TEST_F(IoBlockTest, SimpleRoute)
{
    //EXPECT_CALL(*_ioType1block, Input(_, _, _)).Times(1);

    EXPECT_CALL(*_ioType1block, Input(_, _, _))
            .Times(1)
            .WillOnce([](int x, int y, int z)
            {
                std::cout << "Input: " << x
                          << " " << y
                          << " " << z << std::endl;
            });

    // Input method trigger code
    //for (size_t i = 0;i < 6; ++i)
    //    _ioType0block->Input(0, 1, 2);
}

int main(int argc, char *argv[])
{
    std::cout << "IoBlockTest unit-tests have been running" << std::endl;
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

When I run the test it passes. And when I un-comment Input method trigger lines it passes too.

I tried to investigate that behavior, but unsuccessfully. I noticed only one thing, when I comment that line:

_ioType0block->SetOutputObject(_ioType1block);

my test is failed as I expected.

If someone knows the reason of that behavior, could you please describe it. Thanks in advance.

Test-environment:

OS: 5.10.34-1-MANJARO

gtest: 1.10.0

Compiler: g++ (10.2.0)

Upvotes: 0

Views: 949

Answers (1)

3CxEZiVlQ
3CxEZiVlQ

Reputation: 38784

The hint is here:

// gtest reports shared_ptr memory leak
::testing::Mock::AllowLeak(_ioType1block.get());

Your mocked object leaks, thus it is never destroyed, thus the number of mocked calls is never compared with the expected 1.

The leak happens because of the circular dependencies. You must break these dependencies with help of std::weak_ptr in the children or using std::unique_ptr and raw pointers in the children instead of std::shared_ptr.

Upvotes: 3

Related Questions