Unit testing is all about testing one piece of functionality in isolation. This is very easy when the subject of your test doesn’t involve any dependencies. Take the following method:
<?php
class Tag
{
public function normalizeName($name)
{
$normal = trim($name);
$normal = strtolower($normal);
return $normal;
}
}
Testing this method requires just an input and an expected output. This is a good case for using a PHPUnit dataProvider:
<?php
class TagTest extends PHPUnit_Framework_TestCase
{
/**
* @covers Tag::normalizeName
* @dataProvider provideNormalizeName
*/
public function testNormalizeName($input, $expected)
{
$tag = new Tag();
$this->assertEquals($expected, $tag->normalizeName($input));
}
public function provideNormalizeName()
{
return array(
array('foo', 'foo'),
array('FOO', 'foo'),
array(' foo ', 'foo'),
array(' FOO ', 'foo'),
);
}
}
Unfortunately, it’s not always this easy. Let’s say we want to centralize this normalization logic in some sort of reusable Normalizer class, which we inject into the Tag class:
<?php
class Tag
{
protected $normalizer;
public function __construct(Normalizer $normalizer)
{
$this->normalizer = $normalizer;
}
public function normalizeName($name)
{
return $this->normalizer->normalize($name);
}
}
The test above still passes, but we are now testing two things: the normalizeTag method and the Normalizer class; we are no longer testing our method in isolation.
Mock objects to the rescue!
To restore this isolation we create a “mock” version of the Normalizer class and tell that mock exactly what we expect will be done to it and how we want it to respond.
In PHPUnit 3.4, creating this mock object would look something like this:
<?php
class TagTest extends PHPUnit_Framework_TestCase
{
/**
* @covers Tag::normalizeName
*/
public function testNormalizeName()
{
$input = 'foo';
$expected = 'foo';
// the final "false" tells PHPUnit not to call the Normalizer
// constructor because it requires another dependency, which we don't
// care about right now
$normalizer = $this->getMock('Normalizer', array(), array(), '', false);
$normalizer
->expects($this->once())
->method('normalize')
->with($input)
->will($this->returnValue($expected));
$tag = new Tag($normalizer);
$this->assertEquals($expected, $tag->normalizeName($input));
}
}
Most of this is quite readable, thanks to Sebastian’s nice mocking API. However, the initial getMock method is a bit gnarly. I’ve added a comment so you understand what’s going on, which I’d rather not have to do.
In PHPUnit 3.5 we can use the new mock builder object, a contribution of Giorgio Sironi, which makes our test code that much more readable:
<?php
class TagTest extends PHPUnit_Framework_TestCase
{
/**
* @covers Tag::normalizeName
*/
public function testNormalizeName()
{
$input = 'foo';
$expected = 'foo';
$normalizer = $this->getMockBuilder('Normalizer')
->disableOriginalConstructor()
->getMock();
$normalizer
->expects($this->once())
->method('normalize')
->with($input)
->will($this->returnValue($expected));
$tag = new Tag($normalizer);
$this->assertEquals($expected, $tag->normalizeName($input));
}
}
So there you go: unit tests, mocking, and the new mock builder.
by Kris Wallsmith
4:00 pm • 29 October 2010 • 10 notes | osky-tech-admin