When testing with PHPUnit, we're working with mock objects or real objects, depending what we want to achieve. We're using the real objects when we want the real functionality to test the output and we're using the mocks when we only need dummies or want to have control that can be asserted over the results of the desired methods.
When you're running your tests, you must have a clear understanding of both so you can easily figure out which one to use and why.
I've found myself sometimes feeling like there's something missing, something to give me more control over my tests, and that was the ability to allow code from the original class to be executed on the mocked object.
While some can argue that this is a useless feature that we don't need because real code should be executed on real objects and not the mocks, I still find this to be a pretty cool way to free myself from certain constraints.
With all that being said, let's get into some code.
First, we have our simple Counter
class. This class is extremely simple and it's purpose is to hold a value with we can increment or retrieve, starting from the value that we pass to it's constructor, and it looks as follows:
<?php
class Counter
{
protected $value;
public function __construct($value = 0)
{
$this->value = $value;
}
public function increment()
{
$this->value++;
}
public function get()
{
return $this->value;
}
}
For this simple class, we are going to write a simple test to illustrate the concept.
<?php
class CounterTest extends \PHPUnit_Framework_TestCase
{
public function testExample()
{
$counter = $this->getMockBuilder(Counter::class)
->setConstructorArgs([0])
->getMock();
$this->passThrough($counter, Counter::class, 'get');
$this->passThrough($counter, Counter::class, 'increment');
$this->assertSame(0, $counter->get());
$counter->increment();
$counter->increment();
$counter->increment();
$this->assertSame(3, $counter->get());
}
private function passThrough($object, $realClass, $method)
{
$object->expects($this->any())
->method($method)
->willReturnCallback(function () use ($object, $realClass, $method) {
$refMethod = new ReflectionMethod($realClass, $method);
return $refMethod->invoke($object);
});
}
}
In order to achieve the "pass through" for the functionality, we're going to be using ReflectionMethod
to be able to invoke the closure on the mock object and this is as simple as the above example.
All we have to do is tell PHPUnit that the mock object expects the method we want to pass through will return a callback. In that callback, the magic happens, a ReflectionMethod
object is initialized for the real class and the given method, and the result of invoking that closure is returned.
Once you've done this, you're good to go!
Enjoy your coding!