Testing Code That Uses Singletons

Sebastian Bergmann » 11 February 2010 » in Articles » 12 Comments

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.

Defined tags for this entry: , , ,

Trackback specific URI for this entry

12 Comments to "Testing Code That Uses Singletons"

Display comments as (Linear | Threaded)
  1. John
    11/02/2010 at 16:06 Permalink
    In the few cases where a singleton pattern has made sense in projects I've worked on, I've taken to rolling the Dependency Injections and reset() techniques into one call to the instance getter, to cut down on the additional code needed (forgive the old PHP 4 style of singleton usage, I'm updating some of the older code in this project:)


    function &get_instance($force = false) {
    static $instance;

    if (is_object($force)) {
    $instance = array($force);
    }

    if (!$instance || ($force === true)) {
    $instance = array(new MyClass());
    }

    return $instance[0];
    }

    Reply

  2. Weltraumschaf
    11/02/2010 at 16:28 Permalink
    My opinion about the reset aproach is: that's total crap! I saw this in Zend Framework first time. Why should I implement a Singleton Pattern, when I give every Developer the posibility to reset the Singleton? That makes no sense and is absolute silly, sorry for this hard words. But the reset approach shows me, that the Developer of the Singleton class didn't think about what he did! The test context approach is a workaround for existing legacy code. But I favor the approach to provide a factory class which holds the single instance.

    Reply

  3. Sebastian Bergmann
    11/02/2010 at 16:36 Permalink
    The correct usage of the reset() method needs to be enforced, of course. It should be fairly easy to implement a sniff for PHP_CodeSniffer that checks whether reset() is only called from test code.

    Reply

  4. Matthew Weier O'Phinney
    11/02/2010 at 18:18 Permalink
    I take full responsibility for the fact that we have singletons in ZF; chalk it up to ignorance on my part. That code was written almost four years ago, and at the time, I didn't fully understand the ramifications (though I was running into them due to unit testing).

    In fact, it's exactly my experiences in unit testing Zend_Controller_Front that have pushed me to ban singletons from ZF 2.0 unless there's a really, really good use case. That said, any singleton does need to be testable -- so methods such as reset() or a context switch (like the $testing property) will need to be in place. Yes, they can be abused, but if you're abusing it, you know why you're doing it.

    Reply

  5. Peter
    11/02/2010 at 16:30 Permalink
    Hi Sebastian,

    you have an error in your reset method:
    self::$instance = NULL;

    I think it must be:
    self::$uniqueInstance = NULL

    Greetz Peter

    Reply

  6. Sebastian Bergmann
    11/02/2010 at 16:36 Permalink
    Fixed, thanks!

    Reply

  7. Chuck Burgess
    11/02/2010 at 17:11 Permalink
    The similar alternative I've used before was to subclass my MockSingleton as an extension to Singleton, and put the reset() method only in MockSingleton.

    Reply

  8. Chuck Burgess
    11/02/2010 at 18:09 Permalink
    Granted, there's more context to consider in my own Singleton and MockSingleton implementations. Given that my Singleton is an abstract class with concrete children, I use an abstract factory in Singleton for specifying which concrete child to instantiate. I suppose one could _almost_ argue that's _nearly_ dependency injection ;-)

    Also, all "behavior" that exists for testability lies in MockSingleton, rather than any code whatsoever existing in Singleton specifically to make it testable. I had started to show and/or explain more fully in here, but figured that would be overkill.

    Reply

  9. Steven Grimm
    11/02/2010 at 17:40 Permalink
    A reset() method doesn't actually help you inject any mock objects for testing -- you can call it from a test but you'll still get a real Singleton object next time a client wants one.

    My preferred approach is to provide a setter method that takes a Singleton argument (and enforces that its argument is of the correct type!). Then the test code can wire in a mock singleton with the desired behavior.

    This does not, admittedly, test the "new Singleton" code path, but that's presumably something you want to test in the test class for Singleton itself, not in a test class for a client that *uses* the Singleton object.

    Reply

  10. Sebastian Bergmann
    11/02/2010 at 17:45 Permalink
    Thank you, Steven, for pointing out that I was not clear about what I wanted to achieve with the reset() method.

    The purpose of the reset() method is not to introduce dependency injection or some other refactoring that actually improves the design. The reset() method's purpose is rather to improve the testability of code that uses singletons with only making a single change which is adding the reset() method to the singleton.

    Reply

  11. Josh Adell
    11/02/2010 at 17:59 Permalink
    Another possibility is to build a Singleton::setInstance() method that would allow you to set the mock singleton as $uniqueInstance in the Singleton class. getInstance() would return the mock, and the reset() could be called to clear it out between tests.

    Reply

  12. Chuck Burgess
    11/02/2010 at 22:10 Permalink
    I don't really like the idea of having *any* test-related code in the original class. That was one of my key pieces of criteria as I was evolving my mocks when working on the code I referenced earlier.

    Reply

6 Trackbacks to "Testing Code That Uses Singletons"

  1. Sebastian Bergmann 16/02/2010 at 08:20
    This article is part of a series on testing untestable code: Testing private methods Testing code that uses singletons Stubbing static methods Stubbing hard-coded dependencies In a unit test, mock objects can simulate the behavior of complex, real
  2. Sebastian Bergmann 16/02/2010 at 08:21
    This article is part of a series on testing untestable code: Testing private methods Testing code that uses singletons Stubbing static methods Stubbing hard-coded dependencies With PHPUnit 3.5 it will be possible to stub and mock static methods. C
  3. Sebastian Bergmann 16/02/2010 at 08:21
    This article is part of a series on testing untestable code: Testing private methods Testing code that uses singletons Stubbing static methods Stubbing hard-coded dependencies I frequently quote Miško Hevery with "It is hard to test code that uses
  4. Sebastian Bergmann 16/02/2010 at 08:21
    This article is part of a series on testing untestable code: Testing private methods Testing code that uses singletons Stubbing static methods Stubbing hard-coded dependencies No, not those privates. If you need help with those, this book might hel
  5. www.phparch.com 04/03/2010 at 13:13
  6. www.brandonsavage.net 29/03/2010 at 13:01

Add Comment


To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

Submitted comments will be subject to moderation before being displayed.