Using ReflectionClass To UnitTest in PHP
Monday August 15th
This content assumes using PHPUnit 5.0 and PHP 5.6/7.0(the code has been tested in both versions).
Let's say we want to have a PHP backend that needs to handle an attendee list for a meeting. The following class handles the list and has a function to add an attendee, and an another to send an email to all of them.
<?php
class AttendeeList
{
//Array containing all of attendees
private $attendees;
//Initializes our list
public function __construct()
{
$this->attendees = [];
}
//Adds an attendee in our list
public function addAttendee($name, $email)
{
$this->attendees[] = ["name" => $name, "email" => $email];
}
//Sends an email to all of our attendees
public function sendEmail($email)
{
//The implementation of this function does not matter for this example
}
}
?>
We currently want to write a unit test for the addAttendee function. The point of this test is to verify that the attendee has currently been added to the list. The issue is, we have no way of easily checking the list, since it is private. Let's look at the solutions we have:
Make attendees public
Just making the object public would make the array available to everyone. This would just break the OO encapsulation principle. Not wrapping this array could easily break the application.
Make a "GetAttendees" function
This wouldn't break any OO principle, but it adds extra code that requires maintenance and is only used in a unit test.
Using a Reflection Class
The whole point of this post. This does not require any additional code in out current class. Our unit test will be a few lines bulkier, but maintainability won't be an issue.
Let's look at the basic structure of our test class first :
<?php
use PHPUnit\Framework\TestCase;
class AttendeeListTest extends TestCase
{
//Tests Instance of the class
public function testInstantiation(){
$list = new AttendeeList();
$this->assertInstanceOf(AttendeeList::class, $list);
}
//Tests Sending Email
public function testSendEmail(){
//This implementation is not important
}
//Tests Adding an attendee to our list
public function testAddAttendee(){
//We need to write code here
}
}
?>
Our goal is to write a clean testAddAttendee function. We'll start by instanciating it and calling it.
$list = new AttendeeList();
$list->addAttendee('Grase', 'somebody@google.com');
We should expect to currently have a single array in our list. We will add a second one, to make sure it adds both correctly.
$list->addAttendee('Jael', 'myname@is.me');
We now need to get data from the list.
$reflectionList = new \ReflectionClass($list);
What's a ReflectionClass? It's an abstraction of the object we passed to it. It contains a lot of useful information about the class structure. We can obtain the class name, the methods, the properties and a lot more by using its public methods.
Our goal is currently to see if we have a correct list of attendees. Let's take a look at the property.
$reflectionAttendees = $reflectionList->getProperty('attendees');
We now have a ReflectionProperty object. It serves the same purpose as the ReflectionClass, but contains informations about the property itself. We'll run into an issue fairly quickly though. This property is private. We still won't be able to access it. Let's change that.
$reflectionAttendees->setAccessible(true);
Everything is set! Let's now test if our array fits our needs.
$expected = [["name" => "Grase", "email" => "somebody@google.com"], ["name" => "Jael", "email" => "myname@is.me"]];
$this->assertEquals($expected, $reflectionAttendees->getValue($list);
We used getValue to obtain the private property's value from our object. Let's mesh all this together into the class.
<?php
class AttendeeListTest extends TestCase
{
//Tests Instance of the class
public function testInstantiation(){
$list = new AttendeeList();
$this->assertInstanceOf(AttendeeList::class, $list);
}
//Tests Sending Email
public function testSendEmail(){
//This implementation is not important
}
//Tests Adding an attendee to our list
public function testAddAttendee(){
$list = new AttendeeList();
$list->addAttendee('Grase', 'somebody@google.com');
$list->addAttendee('Jael', 'myname@is.me');
$reflectionList = new \ReflectionClass($list);
$reflectionAttendees = $reflectionList->getProperty('attendees');
$reflectionAttendees->setAccessible(true);
$expected = [["name" => "Grase", "email" => "somebody@google.com"], ["name" => "Jael", "email" => "myname@is.me"]];
$this->assertEquals($expected, $reflectionAttendees->getValue($list));
}
}
?>
And if we run this test in PHPUnit, we get :
PHPUnit 5.5.0 by Sebastian Bergmann and contributors.
... 3 / 3 (100%)
Time: 16 ms, Memory: 4.00MB
OK (3 tests, 2 assertions)
If you want to dive further in Reflection Objects, look up ReflectionClass on PHP's site
Want to stay up to date on my publications? Follow me on Twitter, or subscribe to my Blog's RSS Feed.