Reflection is often used as part of software testing, such as for 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:
Car::getColor()
which is out of the scope of this testCar::getColor()
which may have a bug itself which can make the test have a false positive or negativeLet's look at why we shouldn't use Car::getColor()
in our unit test and should use Reflection instead. Let's say a developer 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 Refelection to inspect the protected member variable directly. So how do we do that? We can use Refelection 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:
Car::$color
(this "represents" the Car::$color
variable)Car::$color
accessibleCar::$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.