Very few times, is it OK to write your own mock class, by hand, for an existing class. We’ll save the nitty gritty of that statement for a later rant.
If you happen across the very rare instance that you need a hand-written mock, then you will definitely want to write a unit test for your mock. Otherwise, in a unit test, you should never test a mock.
Many times I have seen this awful pattern.
There is a class like so
<?php
class MyClass {
public function doAwesome() {
$param1 = ... // compute this
$param2 = ... // compute this
$silly_bar = $this->doSomethingIDontWantToDoWhenITest($param1, $param2);
return $silly_bar;
}
protected function doSomethingIDontWantToDoWhenITest($param1, $param2) {
// Literally -- Do something that I don't want to do when I test doAwesome
}
}
?>
with a test like so
<?php
require_once 'AutoLoader.php';
class MyClassTest extends PHPUnit_TestCase_Framework {
function testDoAweome() {
$my_obj = new MyClassMock();
$this->assertEquals("I'm awesome", $my_obj->doAwesome());
}
}
class MyClassMock extends MyClass {
/**
* We'll just return some string to make sure this gets called.
*/
protected function doSomethingIDontWantToDoWhenITest($param1, $param2) {
return "I'm awesome";
}
}
?>
First, if I look at the test, it looks like I am testing a MyClassMock, and not MyClass.
Maybe I should rename this test to MyClassMockTest. WRONG
Why are you testing the mock?
The mock isn’t going to be used anywhere!
Why did the person even write this mock in the first place?
MyClass calls doSomethingIDontWantToDoWhenITest in the doAwesome method that I actually want to test, so if we make the doSomethingIDontWantToDoWhenITest method protected, then I can extend MyClass and override doSomethingIDontWantToDoWhenITest to return something simple without doing any work.
So clever, but SO WRONG!
This is wrong, and this example has put us in an awful trap.
The method doAwesome computes $param1 and $param2 before passing them to doSomethingIDontWantToDoWhenITest and returns the result of that call. Our MyClassMock does not take into account what got computed for $param1 and $param2. Sure, we executed the code to compute $param1 and $param2, but we only verified the results of the mock implementation of doSomethingIDontWantToDoWhenITest. * Slaps Forehead *
How do we fix this?
We have uncovered that doSomethingIDontWantToDoWhenITest is essentially begging for the Strategy Pattern.
Essentially, we need to extract method into a new object, like so
<?php
class MyStrategy {
public function doSomethingIDontWantToDoWhenITest($param1, $param2) {
// Literally -- Do something that I don't want to do when I test doAwesome
}
}
?>
and modify MyClass like so
<?php
class MyClass {
private $strategy;
public MyClass($strategy) {
$this->strategy = $strategy;
}
public function doAwesome() {
$param1 = ... // compute this
$param2 = ... // compute this
$silly_bar = $this->strategy->doSomethingIDontWantToDoWhenITest($param1, $param2);
return $silly_bar;
}
}
?>
and modify the test, like this
<?php
class MyClassTest extends PHPUnit_Framework_TestCase {
function testDoAwesome() {
$param1 = ... // what the value of this should be computed by doAwesome to be
$param2 = ... // what the value of this should be computed by doAwesome to be
$strategy = $this->getMock('MyStrategy');
$strategy
->expects($this->once())
->method('doSomethingIDontWantToDoWhenITest')
->with($param1, $param2)
->will($this->returnValue("I'm truly awesome"));
$my_object = new MyClass($strategy);
$this->assertEquals("I'm truly awesome", $my_object->doAwesome());
}
?>
The PHPUnit mock object library in this case will verify that the method doSomethingIDontWantToDoWhenITest is called once, with what we expect doAwesome will compute $param1 and $param2 to be, and will then return a value that we can verify gets returned un-altered.
EXTRA BONUS !!!
Besides no longer be confused about what is actually being tested. I improved the re-usability of MyClass. Anyone can now make several MyClass objects that handle $param1 and $param2 differently because anyone can implement whatever strategy he wants by simply composing a new MyClass at construction time with a different strategy.
If you find yourself writing a mock, stop and think. You almost always can re-work your code into something that is more extensible and re-usable with a side benefit of better testability.