Mock static method from external Class (that I can't change!)

删除回忆录丶 提交于 2020-12-06 08:09:12

问题


I want to mock (with gmock) a static function from a class that I can't change. A is the class that I want to mock:

Class A
{
public:
   static std::string get_id();
...
}

B is my class that I want to test with gmock:

Class B
{
public:
   B(A *a_ptr);
   ...

   std::string foo();
private:
   A *m_a_ptr;
}

B::B(A *a_ptr) : m_a_ptr(a_ptr)
{
}

std::string B::foo()
{
   id = m_a_ptr->get_id();
   return id;
}

How can I mock the method get_id without changing the class A?


回答1:


Static dependency injection and GMock delegation

We will start by minimizing your example to the following (to keep the passages that follows as non-noisy as possible):

// a.h
#include <string>

// class to mock
class A {
    static std::string get_id();
};

// b.h
#include <string>
#include "a.h"

// class that use A
struct B {
    std::string foo() const {
        return A::get_id();
    }
};

Although you cannot change A, you can change B to statically inject A in product code, whereas you can statically inject a mock delegate of A for test code:

// b.h
#include <string>
#include "a.h"

namespace detail {
// The type template parameter is set to A by default, 
// and should not need to override this default type 
// in production code, but can be injected with 
// mocked classes in test code.
template<typename AImpl = ::A>
struct BImpl {
   std::string foo() const {
        return A::get_id();
   }
};
}  // namespace detail

// Expose product-intent specialization.
using B = BImpl<>;

Where the mock for A make use of static (non-thread safe) approach to mock calls to the injected static type:

// a_mock.h
#include <memory>
#include <string>
#include "gmock/gmock.h"

class AMock {
    // Mocked methods.
    struct Mock {
        MOCK_CONST_METHOD0(get_id,
                           std::string());
    };

    // Stubbed public API for static function of object under test:
    // delegates stubbed calls to the mock.
    static std::string get_id() {
        if (const auto mock = mock_.lock()) {
            mock->get_id();
        }
        else {
            ADD_FAILURE() 
                << "Invalid mock object! The test can no "
                   "longer be considered useful!";   
        }
    }

    // Public setter to specify the mock instance used in test (which in
    // turn will be the instance that Google Test's EXPECTS and mocked
    // calls is placed upon).
    static void setMock(const std::shared_ptr<Mock>& mock) { mock_ = mock; }

  private:
    // Pointer to mock instance.
    static std::weak_ptr<Mock> mock_;
};

which can, finally, be used in tests of BImpl as follows:

// b_test.cpp
#include "b.h"  // object under test
#include "gmock/gmock.h"
#include "a_mock.h"

class BImplTest : public ::testing::Test {
public:
    using BImplUnderTest = BImpl<AMock>;

    BImplTest() : amock_(std::make_shared<AMock::Mock>()) {
        AMock::setMock(amock_);
    }
};

TEST_F(BImplTest, foo) {
    // Setup mocked call(s).
    EXPECT_CALL(amock_, foo()).WillOnce(::testing::Return( /*...*/ ));

    // Call object under test.
    BImplUnderTest b{};
    b.foo();
      
}

Further hiding the fact that B is in fact a specialization of a class template BImpl

If you start to heavily use this pattern (in a sliding-window manner over different sub-routines) and want to avoid single large and bloated translation units, you could move the definitions of the member functions of the detail::B class template to separate header, say b-timpl.h (which includes b.h) and in the source file associated with b.h, say b.cpp, include b-timpl.h instead of b.h and add an explicit instantiation definition for the production intent detail::BImpl specialization:

// b.cpp
template class ::detail::BImpl<>;

Whereas in tests of ::detail::BImpl you include b-timpl.h instead of b.h and add an explicit instantiation definition for the mock-injected specialization of the class template:

// b_test.cpp
#include "b-timpl.h"
// ...

template class ::detail::BImpl<AMock>;

// ...

Why? The BImpl class is not parameterized to allow a user of its interface to statically inject different behahaviour (for user intent, users should only see B), but to allow injecting mocked or stubbed classes while testing.



来源:https://stackoverflow.com/questions/63630216/mock-static-method-from-external-class-that-i-cant-change

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!