Stubbing Hard-Coded Dependencies
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 (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.
16/02/2010 at 09:01 Permalink
Reply
20/01/2011 at 23:46 Permalink
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
22/12/2011 at 14:55 Permalink
Much appriciated
/Marcus
Reply