This guide demonstrates techniques for mocking singleton template classes using Google Mock.
Method 1: Direct Mocking via Static Method
For a simple singleton template class, you can directly mock its methods using GMock's MOCK_METHOD macro. This approach assumes the singletno class already exposes methods intended for mocking.
#include <gtest/gtest.h>
#include <gmock/gmock.h>
using ::testing::Return;
using ::testing::AnyNumber;
using ::testing::_;
template<typename T>
class MockSingletonFoo {
public:
// Static method to access the singleton instance
static MockSingletonFoo& GetInstance() {
static MockSingletonFoo instance;
return instance;
}
// GMock method for mocking
MOCK_METHOD(T, performOperation, (const T&), ());
private:
// Private constructor and destructor to enforce singleton pattern
MockSingletonFoo() = default;
~MockSingletonFoo() = default;
MockSingletonFoo(const MockSingletonFoo&) = delete;
MockSingletonFoo& operator=(const MockSingletonFoo&) = delete;
};
// Test fixture for Foo tests
class FooTest : public ::testing::Test {
protected:
void SetUp() override {
// No specific setup needed for this basic case
}
void TearDown() override {
// No specific teardown needed for this basic case
}
};
// Test case for mocking a singleton operation
TEST_F(FooTest, MockOperation)
{
auto& singletonInstance = MockSingletonFoo<int>::GetInstance();
// Expect the performOperation method to be called once with any argument, returning 42
EXPECT_CALL(singletonInstance, performOperation(_)).WillOnce(Return(42));
// Assert that calling the method returns the mocked value
EXPECT_EQ(singletonInstance.performOperation(10), 42);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Installasion (Red Hat-based systems):
sudo dnf install gtest gtest-devel gmock-devel
Compilation:
g++ -std=c++17 main.cpp -lgtest -lgmock -lpthread -o test
Method 2: Using a Mockable Base Class with Instance Replacement
In scenarios where the original singleton class doesn't expose mockable methods directly, you can create a derived mock class and provide a mechanism to replace the singleton instance during testing.
Using std::unique_ptr:
This approach uses a std::unique_ptr to manage the singleton instance, preventing potential memory leaks and simplifying instance management.
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <memory>
using ::testing::_;
using ::testing::Return;
template <typename T>
class SingletonBase {
private:
static std::unique_ptr<SingletonBase> instance_ptr; // Use unique_ptr for safer memory management
public:
// Static method to get the singleton instance
static SingletonBase& GetInstance() {
if (!instance_ptr) {
instance_ptr = std::make_unique<SingletonBase>();
}
return *instance_ptr;
}
// Static method to replace the singleton instance for testing
static void SetInstance(std::unique_ptr<SingletonBase> newInstance) {
instance_ptr = std::move(newInstance);
}
// Virtual method to allow overriding in mock class
virtual T processValue(const T& input) {
return input; // Default implementation
}
// Public constructor and virtual destructor are essential for polymorphism and proper cleanup
SingletonBase() = default;
virtual ~SingletonBase() = default;
// Disable copy and assignment to maintain singleton property
SingletonBase(const SingletonBase&) = delete;
SingletonBase& operator=(const SingletonBase&) = delete;
};
// Definition of the static member
template <typename T>
std::unique_ptr<SingletonBase<T>> SingletonBase<T>::instance_ptr = nullptr;
// Derived mock class inheriting from the base singleton
template <typename T>
class MockSingleton : public SingletonBase<T> {
public:
// Mock the virtual method
MOCK_METHOD(T, processValue, (const T&), (override));
};
// Test fixture for SingletonBase tests
class SingletonBaseTest : public ::testing::Test {
protected:
void SetUp() override {
// Setup can be empty or customized as needed
}
void TearDown() override {
// Teardown can be empty or customized as needed
}
};
// Test case 1: Verify mocking with unique_ptr
TEST_F(SingletonBaseTest, MockWithUniquePtr1)
{
// Create a mock instance using unique_ptr
auto mockInstance = std::make_unique<MockSingleton<int>>();
// Set expectations on the mock object
EXPECT_CALL(*mockInstance, processValue(_)).WillOnce(Return(42));
// Replace the actual singleton instance with the mock instance
SingletonBase<int>::SetInstance(std::move(mockInstance));
// Call the method on the singleton instance and assert the result
EXPECT_EQ(SingletonBase<int>::GetInstance().processValue(1), 42);
}
// Test case 2: Another test to ensure isolation
TEST_F(SingletonBaseTest, MockWithUniquePtr2)
{
auto mockInstance = std::make_unique<MockSingleton<int>>();
EXPECT_CALL(*mockInstance, processValue(_)).WillOnce(Return(43));
SingletonBase<int>::SetInstance(std::move(mockInstance));
EXPECT_EQ(SingletonBase<int>::GetInstance().processValue(2), 43);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Using Raw Pointers (Caution Advised):
If std::unique_ptr causes issues or is not suitable, a raw pointer can be used. However, this requires careful manual memory management to avoid leaks and segmentation faults.
#include <gtest/gtest.h>
#include <gmock/gmock.h>
using ::testing::_;
using ::testing::Return;
template <typename T>
class RawSingleton {
private:
static RawSingleton* instance_ptr;
public:
static RawSingleton& GetInstance() {
if (!instance_ptr) {
instance_ptr = new RawSingleton();
}
return *instance_ptr;
}
// Method to replace the singleton instance
static void SetInstance(RawSingleton* newInstance) {
delete instance_ptr; // Clean up the old instance
instance_ptr = newInstance;
}
// Method to release the singleton instance (optional, useful for cleanup)
static void ReleaseInstance() {
delete instance_ptr;
instance_ptr = nullptr;
}
virtual T processRawValue(const T& input) {
return input; // Default implementation
}
RawSingleton() = default;
virtual ~RawSingleton() = default; // Virtual destructor is crucial
RawSingleton(const RawSingleton&) = delete;
RawSingleton& operator=(const RawSingleton&) = delete;
};
// Definition of the static member
template <typename T>
RawSingleton<T>* RawSingleton<T>::instance_ptr = nullptr;
// Mock class inheriting from RawSingleton
template <typename T>
class MockRawSingleton : public RawSingleton<T> {
public:
MOCK_METHOD(T, processRawValue, (const T&), (override));
};
// Test fixture for RawSingleton tests
class RawSingletonTest : public ::testing::Test {
protected:
MockRawSingleton<int>* mock_ptr = nullptr;
void SetUp() override {
// Create a mock instance and set it as the singleton instance
mock_ptr = new MockRawSingleton<int>();
RawSingleton<int>::SetInstance(mock_ptr);
}
void TearDown() override {
// Reset the singleton instance and clean up the mock object
RawSingleton<int>::ReleaseInstance(); // Important to prevent memory leaks
// delete mock_ptr; // SetInstance already deleted the previous instance, and ReleaseInstance nullifies it. Manual delete here could be problematic if not handled carefully.
}
};
// Test case 1: Verify mocking with raw pointers
TEST_F(RawSingletonTest, MockWithRawPointer1)
{
// Set expectations on the mock object
EXPECT_CALL(*mock_ptr, processRawValue(_)).WillOnce(Return(42));
// Call the method on the singleton instance and assert the result
EXPECT_EQ(RawSingleton<int>::GetInstance().processRawValue(1), 42);
}
// Test case 2: Another test case for isolation
TEST_F(RawSingletonTest, MockWithRawPointer2)
{
EXPECT_CALL(*mock_ptr, processRawValue(_)).WillOnce(Return(43));
EXPECT_EQ(RawSingleton<int>::GetInstance().processRawValue(2), 43);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Choosing between std::unique_ptr and raw pointers depends on the specific constraints and memory management strategy of your project. std::unique_ptr general offers safer and more idiomatic C++ memory handling.