Using Reflection for Better Unit Testing in PHP

Accessing private and protected member variables

Reflection is often used as part of software testing that requires the runtime creation/instantiation of mock objects. It’s also great for inspecting the state of an object at any given point in time. Here’s an example of using Reflection in a unit test to verify a protected class member contains the expected value.

Below is a very basic class for a Car. It has a protected member variable that will contain the value representing the color of the car. Because the member variable is protected we cannot access it directly and must use a getter and setter method to retrieve and set its value respectively.

class Car
{
    protected $color
 
    public function setColor($color)
    {
        $this->color = $color;
    }
 
    public function getColor($color)
    {
        return $this->color;
    }
}

To test this many developers will create a Car object, set the car’s color using Car::setColor(), retrieve the color using Car::getColor(), and compare that value to the color they set:

 /**
 * @test
 * @covers     \Car::setColor
 */
public function testSetColor()
{
    $color = 'Red';
 
    $car = new \Car();
    $car->setColor($color);
    $getColor = $car->getColor();
 
    $this->assertEquals($color, $reflectionColor);
}

On the surface this seems okay. After all, all Car::getColor() does is return the value of the protected member variable Car::$color. But this test is flawed in two ways:

  • It exercises Car::getColor() which is out of the scope of this test
  • It depends on Car::getColor() which may have a bug itself which can make the test have a false positive or negative

Let’s look at why we shouldn’t use Car::getColor() in our unit test and should use Reflection instead. Let’s say a developer for a mobilnett casino is assigned a task to add “Metallic” to every car color. So they attempt to modify the Car::getColor() to prepend “Metallic” to the car’s color:

class Car
{
    protected $color
 
    public function setColor($color)
    {
        $this->color = $color;
    }
 
    public function getColor($color)
    {
        return "Metallic "; $this->color;
    }
}

Do you see the error? The developer used a semi-colon instead of the concatenation operator in an attempt to prepend “Metallic” to the car’s color. As a result, whenever Car::getColor() is called, “Metallic ” will be returned regardless of what the car’s actual color is. As a result our Car::setColor() unit test will fail even though Car::setColor() works perfectly fine and was not affected by this change.

So how do we verify Car::$color contains the value we are setting via Car::setColor()? We can use Reflection to inspect the protected member variable directly. So how do we do that? We can use Reflection to make the protected member variable accessible to our code so it can retrieve the value.

Let’s see the code first and then break it down:

/**
 * @test
 * @covers     \Car::setColor
 */
public function testSetColor()
{
    $color = 'Red';
 
    $car = new \Car();
    $car->setColor($color);
 
    $reflectionOfCar = new \ReflectionObject($car);
    $protectedColor = $reflectionOfForm->getProperty('color');
    $protectedColor->setAccessible(true);
    $reflectionColor = $protectedColor->getValue($car);
 
    $this->assertEquals($color, $reflectionColor);
}

Here is how we are using Reflection to get the value of Car::$color in the code above:

  1. We create a new ReflectionObject representing our Car object
  2. We get a ReflectionProperty for Car::$color (this “represents” the Car::$color variable)
  3. We make Car::$color accessible
  4. We get the value of Car::$color

As you can see by using Reflection we could get the value of Car::$color without having to call Car::getColor() or any other accessor function which could cause invalid test results. Now our unit test for Car::setColor() is safe and accurate.

Leave a Reply

Your email address will not be published. Required fields are marked *