Daniel Rotter Web Developer

Testing traits with non-public methods

tags php, testing, traits
time 10 Apr 2014

In one of our last Sulu Pull Request we made use the quite new PHP feature traits. We used it for two small functions, which should help us to read some values from a symfony request object. The trait looks like the following:

trait RequestParametersTrait
{
    protected function getRequestParameter(
        Request $request,
        $name,
        $force = false,
        $default = null
    )
    {
        $value = $request->get($name, $default);
        if ($force && $value === null) {
            throw new MissingParameterException(
                get_class($this),
                $name
            );
        }
        return $value;
    }

    protected function getBooleanRequestParameter(
        Request $request,
        $name,
        $force = false,
        $default = null
    )
    {
        $value = $this->getRequestParameter(
            $request,
            $name,
            $force,
            $default
        );
        if ($value === 'true') {
            $value = true;
        } elseif ($value === 'false') {
            $value = false;
        } elseif ($force && $value !== true && $value !== false) {
            throw new ParameterDataTypeException(
                get_class($this),
                $name
            );
        }

        return $value;
    }
}

As it turned out, it was not that easy to test this kind of code, because the two methods in the trait are protected. But I will go through this step by step.

The first thing that I have found out, is that traits cannot be handled by PHPUnit before version 3.8, but starting with this version there are two possibilities to handle traits: First there is the getMockForTrait-method, which creates a mock for the given trait. But I used the undocumented getObjectForTrait-method, which just returns an object using the given trait. So I came up with the following setup:

class RequestParametersTraitTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @var RequestParametersTrait
     */
    private $requestParametersTrait;

    public function setUp()
    {
        $this->requestParametersTrait = $this->getObjectForTrait(
            'Sulu\Component\Rest\RequestParametersTrait'
        );
    }
}

The next problem was that the methods of the trait have been protected, and therefore could not be used in the test directly. I already knew that it is easily possible to test your privates on a usual class, but the ReflectionMethod-Class didn’t seem to work correctly with traits when used like in the following lines:

$getRequestParameterReflection = new ReflectionMethod(
    'Sulu\Component\Rest\RequestParametersTrait',
    'getRequestParameter'
);
$getRequestParameterReflection->setAccessible(true);
$getRequestParameterReflection->invoke(
    $this->requestParametersTrait,
    [...]
);

It just kept throwing an exception saying that the given object is not of the defined class. So I tried to solve this issue, and after some time I came up with a working solution. It was as easy as using the return value of the get_class-method instead of the hardcoded string:

$getRequestParameterReflection = new ReflectionMethod(
    get_class($this->requestParametersTrait),
    'getRequestParameter'
);
$getRequestParameterReflection->setAccessible(true);
$getRequestParameterReflection->invoke(
    $this->requestParametersTrait,
    [...]
);

This works because PHPUnit really creates a new object with its own class, on which the ReflectionMethod seems to work again. For a better understanding you can have a look at the working example.