Skip to content

Latest commit

 

History

History
142 lines (103 loc) · 9.65 KB

File metadata and controls

142 lines (103 loc) · 9.65 KB

SingletonBase

A header-only C++ library that makes singletons unit testable without breaking existing code or APIs.

The Problem

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.

The Solution

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

Key Benefits

For Singleton Users

  • 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

For Singleton Developers

  • 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

How It Works

Retrofitting Existing Singletons

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;
};

Creating New Testable Singletons

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_;
};

Testing Made Simple

#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
}

Technical Details

  • Header-only: No compilation or linking required
  • Two files: SingletonBase.h (core) and ScopedSingletonState.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

Library Components

  • PM::SingletonBase<T>: CRTP base class providing the singleton implementation
  • PM::ScopedSingletonState<T>: RAII class for test-time instance replacement
  • Macros: PM_SINGLETON, PM_SINGLETON_SAFE_* for cross-library support

Documentation Structure

Getting Started

  1. Include the headers in your project
  2. Add PM_SINGLETON macro to your singleton class
  3. Remove custom instance() method (let SingletonBase provide it)
  4. Use ScopedSingletonState in tests for isolation

See Advanced Topics for best practices and Build Integration for setup instructions.