A header-only C++ library that makes singletons unit testable without breaking existing code or APIs.
Traditional singletons are notoriously difficult to unit test. Their global state and hardcoded instance access make it nearly impossible to:
- Isolate tests from each other due to shared global state
- Mock singleton behavior for testing different scenarios
- Test code that depends on singletons without complex setup
- Retrofit existing singletons for testability without breaking existing users
This creates a fundamental conflict: singletons provide architectural benefits but become testing liabilities.
SingletonBase see SingletonBase class solves this problem by providing a CRTP-based singleton framework that makes any singleton instantly testable while preserving 100% API compatibility. The key innovation is ScopedSingletonState see ScopedSingletonState class - an RAII class that temporarily replaces singleton instances during testing, enabling:
- Complete test isolation with fresh singleton instances per test
- Mocking support through derived classes
- Zero API changes - existing singleton users are unaffected
- No testing framework dependencies - works with any testing approach
- Zero cost: No performance overhead or API changes
- No complexity: Singleton behavior remains exactly the same
- No dependencies: Your code doesn't pay for testability features
- Simple retrofit: Add 2 lines to make existing singletons testable
- Preserved API: All existing methods and behavior unchanged
- Testing freedom: Use any testing techniques you're familiar with
- Mocking support: Create test-specific singleton implementations
Transform any existing singleton into a testable one:
// Before: Untestable singleton
class MySingleton {
public:
static MySingleton& instance() {
static MySingleton inst;
return inst;
}
// ... methods
private:
MySingleton() = default;
};// After: Fully testable with same API
#include <PM/SingletonBase.h>
class MySingleton : public PM::SingletonBase<MySingleton> {
PM_SINGLETON(MySingleton) // Add this line
public:
static MySingleton& instance() { // Remove custom implementation
// SingletonBase provides this automatically
}
// ... all existing methods unchanged
private:
MySingleton() = default;
};Build testable singletons from the start:
#include <PM/SingletonBase.h>
class ConfigManager : public PM::SingletonBase<ConfigManager> {
PM_SINGLETON(ConfigManager)
public:
void setConfig(const std::string& key, const std::string& value);
std::string getConfig(const std::string& key) const;
private:
ConfigManager() = default;
std::unordered_map<std::string, std::string> config_;
};#include <PM/ScopedSingletonState.h>
void testConfigManager() {
// Get fresh instance for this test
PM::ScopedSingletonState<ConfigManager> testState;
// Test with isolated state
ConfigManager::instance().setConfig("key", "value");
assert(ConfigManager::instance().getConfig("key") == "value");
// Automatic cleanup - no test interference
}- Header-only: No compilation or linking required
- Two files:
SingletonBase.h(core) andScopedSingletonState.h(testing) - No STL dependencies: Pure C++ implementation
- Cross-platform: Works on Windows, Linux, macOS
- C++17 compatible: Uses modern C++ features safely
- Thread-safe: Proper synchronization for concurrent access
PM::SingletonBase<T>: CRTP base class providing the singleton implementationPM::ScopedSingletonState<T>: RAII class for test-time instance replacement- Macros:
PM_SINGLETON,PM_SINGLETON_SAFE_*for cross-library support
- SingletonBase - API reference for SingletonBase
- ScopedSingletonState - API reference for ScopedSingletonState
- Advanced Topics - Best practices, tips, and advanced use cases
- Build Integration - Integration with build systems and building from source
- Include the headers in your project
- Add
PM_SINGLETONmacro to your singleton class - Remove custom
instance()method (let SingletonBase provide it) - Use
ScopedSingletonStatein tests for isolation
See Advanced Topics for best practices and Build Integration for setup instructions.