Search code examples
c++unit-testingmockinggoogletest

GTest, expect call on private pointer member


I have a class A such as :


// .h
class A {
public:
    A();
    void init();
private:
    B* b;
    C* c;
};

// .cpp

A::A()
{
    b = new B();
    c = new C();
}

A::init()
{
    if (b->foo()){
        // doing hardware operations
        c->on();
    }
    else
    {
        // not doing hardware operations
    }
}

with b.foo() a method a B returning a boolean value depending a specific B conditions and c::on() some method doing operations on hardware.

I'm unit testing class A with gtest / gmock. I know that theorically what is private should remain private, but the whole purpose of my A::init method is to do a specific operation depending on the result of b::foo() which i cannot mock has b is private.

Is there a way to verify that depending on b::foo() result, my public method A is doing the right operation ?


Solution

  • You need dependency injection.

    Assuming that B and C are representing direct operation on hardware you should abstract this functionality to be able to mock hardware (those B and C).

    This can go like this:

    // InterfaceBC.h
    class IB {
    public:
        virtual ~IB() { }
    
        virtual bool foo() const = 0;
    };
    
    class IC {
    public:
        virtual ~IC() { }
    
        virtual void on() = 0;
    };
    
    // A.h
    class A {
    public:
        explicit A(IB* instanceB, IC* instanceC);
        void init();
    
    private:
        IB* const b;
        IC* const c;
    };
    
    // A.cpp
    A::A(IB* instanceB, IC* instanceC)
        : b { instanceB }
        , c { instanceC }
    {
    }
    
    void A::init()
    {
        if (b->foo()) {
            // doing hardware operations
            c->on();
        } else {
            // not doing hardware operations
        }
    }
    

    Then you can write proper test for it:

    // A_test.cpp
    using testing::Return;
    
    class MockB : public IB {
    public:
        MOCK_METHOD(bool, foo, (), (const override));
    };
    
    class MockC : public IC {
    public:
        MOCK_METHOD(void, on, (), (override));
    };
    
    class StackOverflowTestA : public ::testing::Test
    {
    public:
        MockB mockB;
        MockC mockC;
        A underTest{&mockB, &mockC};
    };
    
    TEST_F(StackOverflowTestA, initOnFooSuccessCOnIsCalled)
    {
        EXPECT_CALL(mockB, foo()).WillOnce(Return(true));
        EXPECT_CALL(mockC, on()).Times(1);
        underTest.init();
    }
    
    TEST_F(StackOverflowTestA, initOnFooFailureCOnIsNotCalled)
    {
        EXPECT_CALL(mockB, foo()).WillOnce(Return(false));
        EXPECT_CALL(mockC, on()).Times(0);
        underTest.init();
    }
    

    Live demo.