How to use PHP’s ReflectionClass to test private methods and properties with PHPUnit

The other day I stumbled across PHP’s ReflectionClass that is available in version 5.3.2 or newer. It is a fantastic tool that allows you to grab information about a class and, in turn, any of its runtime objects. So, when you couple it with with PHPUnit you can easily use it to test private and protected methods and functions of any class.

Up until now, I have always found this to be a predicament in any programming language. You have a lovely class that follows all the rules of object oreiented programming, including encapsualtion and information hiding, that only exposes a small number of methods to the public. In many cases, to keep your code DRY, these public methods rely on private methods to perform task that is very specific to the class and is of no use in the public domain.

So, how do you individually test these private methods of your class without making them public? Well as you may have guessed… PHP’s ReflectionClass is one way of doing so!

The class below is good example, it has the public method validateData that relies on functionality of the constructor, where the incoming data is sorted, and the checkValue method, that ensures that the is a number between 5 and 10.

class Foo {

	private $data = array();

	/**
	 * Creates a new Foo object by taking an array of items
	 * and sorting it before setting the data property
	 * @param  $data
	 */
	function __construct($data) {
		sort($data);
		$this->data = $data;
	}

	/**
	 * Checks whether a value is between 5 and 10
	 * @param  $value
	 * @return bool
	 */
	private function checkValue($value) {
		if ($value && $value > 5 && $value < 10) {
			return true;
		}
		return false;
	}

	/**
	 * Validates the data of this class's data to ensure that all
	 * its values are sorted and between 5 and 10
	 * @return bool
	 */
	public function validateData() {
		$is_valid = true;
		$prev_value = 0;
		foreach ($this->data as $value) {
			if (!$this->checkValue($value) || $prev_value > $value) {
				$is_valid = false;
				break;
			}
			$prev_value = $value;
		}
		return $is_valid;
	}
}

Finally the PHPUnit class below is a comprehensive set of tests for the above class, it uses the ReflectionClass in each of our tests:

  • To verify that our constructor is correctly sorting the incoming array by extracting the private class property ‘data’ and checking that it is, in fact, sorted.
  • To test the private checkValue method to make sure that it conforms to our rules
  • Finally, to test the validateData method with an invalid (not sorted) array to verify it correctly returns false
class DataTest extends PHPUnit_Framework_TestCase {

	/**
	 * Foo::$data is private so lets php reflection
	 * class to test it
	 * @return void
	 */
	public function testConstructor() {
		//First we need to create a ReflectionClass object
		//passing in the class name as a variable
		$reflection_class = new ReflectionClass("Foo");

		//Then we need to get the property we wish to test
		//and make it accessible
		$property = $reflection_class->getProperty('data');
		$property->setAccessible(true);

		//We need to create an empty object to pass to
		//ReflectionProperty's getValue method
		$foo = new Foo(array(7, 6, 8));

		$this->assertEquals(array(6, 7, 8), $property->getValue($foo));
	}

	/**
	 * Foo::checkValue is private so lets php reflection
	 * class to test it
	 * @return void
	 */
	public function testCheckValue()
    {
		$reflection_class = new ReflectionClass("Foo");

		//Then we need to get the method we wish to test and
		//make it accessible
		$method = $reflection_class->getMethod("checkValue");
		$method->setAccessible(true);

		//We need to create an empty object to pass to
		//ReflectionMethod invoke method followed by our
		//test parameters
		$foo = new Foo(null);

		//Boundary test - False expected
		$this->assertFalse($method->invoke($foo, 5));

		//Good data - True Expected
		$this->assertTrue($method->invoke($foo, 7));
    }

	/**
	 * validateData relies on checkValue and the constructor.
	 * So now that, from the tests above we know that these two
	 * work as expected we can test it!
	 * @return void
	 */
    public function testValidateData()
    {
		$foo = new Foo(array(6, 7, 8 ,9));
		$this->assertTrue($foo->validateData());

		$foo = new Foo(array(10));
		$this->assertFalse($foo->validateData());

		//We may also want to test to confirm our rule that
		//the array must be sorted here.

		$reflection_class = new ReflectionClass("Foo");

		//Then we need to get the property we wish to modify,
		//set it to accessible and then override its value
		//with an invalid one
		$property = $reflection_class->getProperty('data');
		$property->setAccessible(true);
		$property->setValue($foo, array(7, 9, 8));

		$this->assertFalse($foo->validateData());
    }
}

Now because each individual method is tested you can easily pinpoint any errors that may be introduced as the code evolves. Obviously this is overkill for a simple class like this, however, you may have more complicated private methods that provide some crucial functionality to a public method that should be individially tested.


Posted In:
Post Details