Full Stack Web Developer
LinkedIn Mastodon GitHub Feed

Working as a Senior Fullstack Developer at Yummy Publishing (previously valantic, Sulu and MASSIVE ART),
lecturing at the Vorarlberg University of Applied Sciences,
founded and co-organizing the VlbgWebDev meetup,
used to co-organize AgentConf.

Testing traits with non-public methods

php, testing, traits

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.