Stubbing Hard-Coded Dependencies

Sebastian Bergmann » 16 February 2010 » in Articles » 3 Comments

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

In a unit test, mock objects can simulate the behavior of complex, real (non-mock) objects and are therefore useful when a real object is difficult or impossible to incorporate into a unit test.

A mock object can be used anywhere in the program where the program expects an object of the mocked class. However, this only works as long as the object can be passed into the context where the original object is used.

Consider the following example:

<?php
require_once 'Bar.php';
 
class Foo
{
    public function doSomething()
    {
        // ...
 
        $bar = new Bar;
        $bar->doSomethingElse();
 
        // ...
 
        return TRUE;
    }
}
?>
<?php
class Bar
{
    public function doSomethingElse()
    {
        print '*';
    }
}
?>

With the code above, it is impossible to run a unit test for the Foo::doSomething() method without also creating an object of Bar. As the method creates the object of Bar itself, we cannot inject a mock object in its stead.

In a perfect world, code such as the above could be refactored using Dependency Injection:

<?php
require_once 'Bar.php';
 
class Foo
{
    public function doSomething(Bar $bar = NULL)
    {
        if ($bar === NULL) {
            $bar = new Bar;
        }
 
        // ...
 
        $bar->doSomethingElse();
 
        // ...
 
        return TRUE;
    }
}
?>

Unfortunately, this is not always possible (not because of technical reasons, though).

This is where the set_new_overload() function that is provided by the test_helpers extension for the PHP Interpreter comes into play. It can be used to register a callback that is automatically invoked when the new operator is executed:

<?php
require_once 'Foo.php';
 
class FooTest extends PHPUnit_Framework_TestCase
{
    protected function setUp()
    {
        $this->getMock(
          'Bar',                    /* name of class to mock     */
          array('doSomethingElse'), /* list of methods to mock   */
          array(),                  /* constructor arguments     */
          'BarMock'                 /* name for mocked class     */
        );
 
        set_new_overload(array($this, 'newCallback'));
    }
 
    protected function tearDown()
    {
        unset_new_overload();
    }
 
    protected function newCallback($className)
    {
        switch ($className) {
            case 'Bar': return 'BarMock';
            default:    return $className;
        }
    }
 
    public function testDoSomething()
    {
        $foo = new Foo;
        $this->assertTrue($foo->doSomething());
    }
}
?>

Lets run this unit test:

PHPUnit 3.4.10 by Sebastian Bergmann.

.

Time: 0 seconds, Memory: 7.50Mb

OK (1 test, 2 assertions)

Note that there is no * (printed from Bar::doSomethingElse()) in the output above.

Defined tags for this entry: ,

Trackback specific URI for this entry

3 Comments to "Stubbing Hard-Coded Dependencies"

Display comments as (Linear | Threaded)
  1. conf
    16/02/2010 at 09:01 Permalink
    Wow! That's really a cool feature for testing old and ugly legacy code. I'll give it a try, thanks!

    Reply

  2. Mike Hedman
    20/01/2011 at 23:46 Permalink
    This is a great way to deal with a persistent problem!

    Sadly, I am not able to install the extension since the pecl installer doesn't seem to work properly on Windows. Do you know of anywhere that a Windows binary is available?

    Another question - would it be possible to also deal with static functions in a similar way? For example, I am testing a function that contains something like:
    $bar = new Bar;
    $bar->doSomethingElse();
    $result = MyClass::doSomething();
    So I could use set_new_overload() to deal with Bar, but I'm envisioning a set_static_overload() that does the same class name substitution when a static call is made.

    I am currently getting around both of these problems by wrapping the includes in the class under test in a condition that evaluates a global variable that I set in the unit test.
    global $isUnitTesting;
    if (!isset($isUnitTesting)) {
    require_once("Bar.php");
    require_once("MyClass.php");
    }
    This way, the normal includes are used when running in normal mode, but skipped when in unit test mode. Then in my test file I extend the dependencies (Bar and MyClass in the above examples) and override the methods I want to mock. This works when running the test case alone, but when I run it in a suite that include tests that end up using the original Bar and MyClass - I get a class redeclaration error.

    Thanks!
    Mike

    Reply

  3. Marcus
    22/12/2011 at 14:55 Permalink
    This article should have more recognition. Great one, and very useful in such cases. I've been googling this solution for days without results. And after getting referred to this I realize what the issue was when trying to achieve Seams in PHP code. This achieves just that and does not alter the real code at all.

    Much appriciated
    /Marcus

    Reply

5 Trackbacks to "Stubbing Hard-Coded Dependencies"

  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. blog.tolleiv.de 08/05/2010 at 18:20

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.