Testing Code That Uses Singletons

This article is part of a series on testing untestable code:

I frequently quote Miško Hevery with

"It is hard to test code that uses singletons."

And then my audience asks me ...

Why is it hard to test code that uses singletons?

Lets have a look at the default implementation of the Singleton design pattern in PHP:

<?php
class Singleton
{
    private static $uniqueInstance = NULL;
 
    protected function __construct() {}
    private final function __clone() {}
 
    public static function getInstance()
    {
        if (self::$uniqueInstance === NULL) {
            self::$uniqueInstance = new Singleton;
        }
 
        return self::$uniqueInstance;
    }
}
?>

The code above declares a class that cannot be instantiated (or cloned) by a client using the new (or clone) operator(s). To get a reference to the only instance of the class one has to use the static method getInstance(). Usually the code that uses the Singleton (which we will refer to as client) is strongly coupled to the getInstance() method:

<?php
class Client
{
    public function doSomething()
    {
        $singleton = Singleton::getInstance();
 
        // ...
    }
}
?>

It is impossible to write a test for the doSomething() method without also invoking the singleton's getInstance() method. This means that we cannot get a fresh instance of the Singleton class and thus have no guarantee that there are no side effects in multiple tests that interact with the singleton.

Dependency Injection

Dependency Injection can help with decoupling the client from the getInstance() method:

<?php
class Client
{
    public function doSomething(Singleton $singleton = NULL)
    {
        if ($singleton === NULL) {
            $singleton = Singleton::getInstance();
        }
 
        // ...
    }
}
?>

Instead of unconditionally invoking the getInstance() method inside the doSomething() we can now optionally pass in an instance of the Singleton class. This allows us to pass in a test-specific equivalent such as a mock object or stub:

<?php
class ClientTest extends PHPUnit_Framework_TestCase
{
    public function testSingleton()
    {
        $singleton = $this->getMock(
          'Singleton', /* name of class to mock     */
          array(),     /* list of methods to mock   */
          array(),     /* constructor arguments     */
          '',          /* name for mocked class     */
          FALSE        /* do not invoke constructor */
        );
 
        // ... configure $singleton ...
 
        $client = new Client;
        $client->doSomething($singleton);
 
        // ...
    }
}
?>

Alternative Singleton Implementations

Either as an alternative or in addition to rewriting the clients to optionally accept an instance of the Singleton class as an argument, we can also rewrite the Singleton class to make testing easier.

Resettable Singleton

The first approach is to add a reset() method to the Singleton class:

<?php
class Singleton
{
    private static $uniqueInstance = NULL;
 
    protected function __construct() {}
    private final function __clone() {}
 
    public static function getInstance()
    {
        if (self::$uniqueInstance === NULL) {
            self::$uniqueInstance = new Singleton;
        }
 
        return self::$uniqueInstance;
    }
 
    public static function reset() {
        self::$uniqueInstance = NULL;
    }
}
?>

Invoking the reset() method causes the getInstance() method to create a fresh object of the Singleton class the next time it is called.

Singleton with Test Context

The second approach is to add a testing context to the Singleton class:

<?php
class Singleton
{
    private static $uniqueInstance = NULL;
    public static $testing = FALSE;
 
    protected function __construct() {}
    private final function __clone() {}
 
    public static function getInstance()
    {
        if (self::$uniqueInstance === NULL ||
            self::$testing) {
            self::$uniqueInstance = new Singleton;
        }
 
        return self::$uniqueInstance;
    }
}
?>

Setting Singleton::$testing = TRUE; causes the getInstance() method to create a fresh object of the Singleton class each time it is called.

PHPUnit Can Help, Too

PHPUnit has a backup/restore mechanism for static attributes of classes.

This is yet another feature of PHPUnit that makes the testing of code that uses global state (which includes, but is not limited to, global and superglobal variables as well as static attributes of classes) easier.

Just Because You Can, Does Not Mean You Should

Yes, it is possible write testable code that uses singletons.
This does not mean, however, that you should use them without thinking twice.