How Is Testing the Registry Pattern or Singleton Hard in PHP

How is testing the registry pattern or singleton hard in PHP?

While it's true that "you can write and run tests aside of the actual program execution so that you are free to affect the global state of the program and run some tear downs and initialization per each test function to get it to the same state for each test.", it is tedious to do so. You want to test the TestSubject in isolation and not spend time recreating a working environment.

Example

class MyTestSubject
{
protected $registry;

public function __construct()
{
$this->registry = Registry::getInstance();
}
public function foo($id)
{
return $this->doSomethingWithResults(
$registry->get('MyActiveRecord')->findById($id)
);
}
}

To get this working you have to have the concrete Registry. It's hardcoded, and it's a Singleton. The latter means to prevent any side-effects from a previous test. It has to be reset for each test you will run on MyTestSubject. You could add a Registry::reset() method and call that in setup(), but adding a method just for being able to test seems ugly. Let's assume you need this method anyway, so you end up with

public function setup()
{
Registry::reset();
$this->testSubject = new MyTestSubject;
}

Now you still don't have the 'MyActiveRecord' object it is supposed to return in foo. Because you like Registry, your MyActiveRecord actually looks like this

class MyActiveRecord
{
protected $db;

public function __construct()
{
$registry = Registry::getInstance();
$this->db = $registry->get('db');
}
public function findById($id) { … }
}

There is another call to Registry in the constructor of MyActiveRecord. You test has to make sure it contains something, otherwise the test will fail. Of course, our database class is a Singleton as well and needs to be reset between tests. Doh!

public function setup()
{
Registry::reset();
Db::reset();
Registry::set('db', Db::getInstance('host', 'user', 'pass', 'db'));
Registry::set('MyActiveRecord', new MyActiveRecord);
$this->testSubject = new MyTestSubject;
}

So with those finally set up, you can do your test

public function testFooDoesSomethingToQueryResults()
{
$this->assertSame('expectedResult', $this->testSubject->findById(1));
}

and realize you have yet another dependency: your physical test database wasn't setup yet. While you were setting up the test database and filled it with data, your boss came along and told you that you are going SOA now and all these database calls have to be replaced with Web service calls.

There is a new class MyWebService for that, and you have to make MyActiveRecord use that instead. Great, just what you needed. Now you have to change all the tests that use the database. Dammit, you think. All that crap just to make sure that doSomethingWithResults works as expected? MyTestSubject doesn't really care where the data comes from.

Introducing mocks

The good news is, you can indeed replace all the dependencies by stubbing or mock them. A test double will pretend to be the real thing.

$mock = $this->getMock('MyWebservice');
$mock->expects($this->once())
->method('findById')
->with($this->equalTo(1))
->will($this->returnValue('Expected Unprocessed Data'));

This will create a double for a Web service that expects to be called once during the test with the first argument to method findById being 1. It will return predefined data.

After you put that in a method in your TestCase, your setup becomes

public function setup()
{
Registry::reset();
Registry::set('MyWebservice', $this->getWebserviceMock());
$this->testSubject = new MyTestSubject;
}

Great. You no longer have to bother about setting up a real environment now. Well, except for the Registry. How about mocking that too. But how to do that. It's hardcoded so there is no way to replace at test runtime. Crap!

But wait a second, didn't we just say MyTestClass doesn't care where the data comes from? Yes, it just cares that it can call the findById method. You hopefully think now: why is the Registry in there at all? And right you are. Let's change the whole thing to

class MyTestSubject
{
protected $finder;

public function __construct(Finder $finder)
{
$this->finder = $finder;
}
public function foo($id)
{
return $this->doSomethingWithResults(
$this->finder->findById($id)
);
}
}

Byebye Registry. We are now injecting the dependency MyWebSe… err… Finder?! Yeah. We just care about the method findById, so we are using an interface now

interface Finder
{
public function findById($id);
}

Don't forget to change the mock accordingly

$mock = $this->getMock('Finder');
$mock->expects($this->once())
->method('findById')
->with($this->equalTo(1))
->will($this->returnValue('Expected Unprocessed Data'));

and setup() becomes

public function setup()
{
$this->testSubject = new MyTestSubject($this->getFinderMock());
}

Voila! Nice and easy and. We can concentrate on testing MyTestClass now.

While you were doing that, your boss called again and said he wants you to switch back to a database because SOA is really just a buzzword used by overpriced consultants to make you feel enterprisey. This time you don't worry though, because you don't have to change your tests again. They no longer depend on the environment.

Of course, you still you have to make sure that both MyWebservice and MyActiveRecord implement the Finder interface for your actual code, but since we assumed them to already have these methods, it's just a matter of slapping implements Finder on the class.

And that's it. Hope that helped.

Additional Resources:

You can find additional information about other drawbacks when testing Singletons and dealing with global state in

  • Testing Code That Uses Singletons

This should be of most interest, because it is by the author of PHPUnit and explains the difficulties with actual examples in PHPUnit.

Also of interest are:

  • TotT: Using Dependency Injection to Avoid Singletons
  • Singletons are Pathological Liars
  • Flaw: Brittle Global State & Singletons

How to test PHP PDO Singleton Class?

I think you are misapplying the Singleton pattern here.

Nevertheless, testing Singletons is possible. Quoting Testing Code that uses Singletons

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.

Also see http://www.phpunit.de/manual/current/en/fixtures.html#fixtures.global-state

The @backupStaticAttributes annotation that is discussed in the section called “@backupStaticAttributes” can be used to control the backup and restore operations for static attributes. Alternatively, you can provide a blacklist of static attributes that are to be excluded from the backup and restore operations like this

So if you wanted to disable the backup, you'd do

class MyPdoTest extends PHPUnit_Framework_TestCase
{
protected $backupStaticAttributesBlacklist = array(
'dbConnection' => array('instance')
);

// more test code
}

Also have a look at the chapter on Database Testing

Is there a use-case for singletons with database access in PHP?

Okay, I wondered over that one for a while when I first started my career. Implemented it different ways and came up with two reasons to choose not to use static classes, but they are pretty big ones.

One is that you will find that very often something that you are absolutely sure that you'll never have more than one instance of, you eventually have a second. You may end up with a second monitor, a second database, a second server--whatever.

When this happens, if you have used a static class you're in for a much worse refactor than if you had used a singleton. A singleton is an iffy pattern in itself, but it converts fairly easily to an intelligent factory pattern--can even be converted to use dependency injection without too much trouble. For instance, if your singleton is gotten through getInstance(), you can pretty easily change that to getInstance(databaseName) and allow for multiple databases--no other code changes.

The second issue is testing (And honestly, this is the same as the first issue). Sometimes you want to replace your database with a mock database. In effect this is a second instance of the database object. This is much harder to do with static classes than it is with a singleton, you only have to mock out the getInstance() method, not every single method in a static class (which in some languages can be very difficult).

It really comes down to habits--and when people say "Globals" are bad, they have very good reasons to say so, but it may not always be obvious until you've hit the problem yourself.

The best thing you can do is ask (like you did) then make a choice and observe the ramifications of your decision. Having the knowledge to interpret your code's evolution over time is much more important than doing it right in the first place.

Best practice on PHP singleton classes

An example singleton classes in php:

Creating the Singleton design pattern in PHP5 : Ans 1 :

Creating the Singleton design pattern in PHP5 : Ans 2 :

Singleton is considered "bad practice".

Mainly because of this: How is testing the registry pattern or singleton hard in PHP?

  • why are singleton bad?

  • why singletons are evil?

  • A good approach: Dependency Injection

  • Presentation on reusability: Decouple your PHP code for reusability

  • Do you need a dependency injection container

  • Static methods vs singletons choose neither

  • The Clean Code Talks - "Global State and Singletons"

  • Inversion of Control Containers and the Dependency Injection pattern

Wanna read more? :

  • What are the disadvantages of using a PHP database class as a singleton?

  • Database abstraction class design using PHP PDO

  • Would singleton be a good design pattern for a microblogging site?

  • Modifying a class to encapsulate instead of inherit

  • How to access an object from another class?

  • Testing Code That Uses Singletons

A Singleton decision diagram (source):

Singleton Decision Diagram

Is DI the only solution to Singleton and/or static objects?

Logging is usually the example where static singletons are OK. You don't need to mock your logging anyway, do you?

How Bad Are Singletons?

Because it's relatively easy to work with singletons, and working without takes much more detailed planning of your application's structure. I asked a question about alternatives some time ago, and got interesting answers.

PHP global object

If you are trying to use the $database object from inside a method of a class, you must use the global keyword, so the $database variable is visible from the method :

class User {
function myMethod() {
global $database;

// Work with $database

}
}


For more informations, take a look at the Variable scope section of the manual.


Another (better) solution, considering you are use a singleton, would be to get that object from the singleton :

class User {
function myMethod() {
$database = Database::Singleton();

// Work with $database

}
}

PHP Singleton design pattern inheritance error

Inheriting Singleton class in PHP is difficult, event in PHP 7.0, but you can do this with some changes on your class to work.

first make your Singleton class to abstract

abstract class Singleton {

}

change your $instance variable to array $instance(s)

private $instances = [];

Now change getInstance() method like below

public static function getInstance() {
if (!isset(self::$instances[static::class]) {
self::$instances[static::class] = new static();
}

return self::$instances[static::class];
}

And change your test

remember now you can't call Singleton:: getInstance() due to abstract

class SingletonChild extends Singleton {
}

class SingletonChildTwo extends SingletonChild {
}

$obj = SingletonChild::getInstance();
$obj_two = SingletonChildTwo::getInstance();
var_dump($obj === SingletonChild::getInstance()); // true
var_dump($obj === $obj_two); // will -> false


Related Topics



Leave a reply



Submit