Testing Traits

The other day I was using the BankAccount sample application during a PHPUnit training. When I showed this code ...

<?php
class Response extends HashMap
{
    // ...
}
?>

... one of the developers said: "That is wrong! A Response is not a HashMap. You should use delegation instead." And, of course, he was right. I replied with "Actually, what we want to do here (once we have PHP 5.4) is to use traits."

I went ahead and refactored the code:

<?php
trait HashMap
{
    protected $values = array();
 
    public function set($key, $value)
    {
        $this->values[$key] = $value;
    }
 
    public function get($key)
    {
        return $this->values[$key];
    }
}
 
class Response
{
    use HashMap;
 
    // ...
}
?>

To make sure that I did not break anything, I ran the test suite for the BankAccount sample application. The tests for classes that previously extended HashMap (Response, for instance) still passed. Of course, the tests for HashMap were now broken because trait cannot be instantiated.

Suddenly I had to think about making traits testable. This is what I came up with:

<?php
class HashMapTest extends PHPUnit_Framework_TestCase
{
    /**
     * @covers HashMap::get
     */
    public function testIsInitiallyEmpty()
    {
        $hashMap = $this->getObjectForTrait('HashMap');
 
        $this->assertAttributeEmpty('values', $hashMap);
 
        return $hashMap;
    }
 
    /**
     * @covers  HashMap::set
     * @covers  HashMap::get
     * @depends testIsInitiallyEmpty
     */
    public function testSettingDataWorks($hashMap)
    {
        $hashMap->set('foo', 'bar');
 
        $this->assertEquals('bar', $hashMap->get('foo'));
    }
}
?>

PHPUnit 3.6 (the current master branch) supports the testing of traits through the new getObjectForTrait() method. This method reuses the mock object code generator and returns an object of a class that uses the trait-under-test.

Along the way, PHP_CodeCoverage and PHP_TokenStream as well as PHPUnit_MockObject needed to be updated for traits, too.