SnapShooter Backups Server, Database, Application and Laravel Backups - Get fully protected with SnapShooter

PHPUnit Tip - Access Private Properties of an Object via Closure

In this tutorial, we share a tip on how we can access a private property of an object without using getter/setter methods.

Sometimes, when writing out tests, to inspect the private properties of our testing class, we have no choice but using setter/getter methods. There setter/getter methods exist solely for the purpose of assisting the tests, which feel wrong to us.

Recently we came across a great article at https://markbakeruk.net/2017/03/05/closures-anonymous-classes-test-mocking-1/. Which shares how we can use Closure to access the private properties of an object.

In this tutorial, we would like to share how we can use Closure in our PHPUnit tests:

Disclaimer: the code samples in this tutorial are for purpose of demonstrating the tactic, they might not be the perfect testing code.

About PHP Closure

In simple terms, a Closure is an anonymous function, but it can access variables outside the scope that it was created.

Most of people have used Closure before, for example, using it in array_filter.

$genius = 'Addison';
 
$users = ['Addison', 'Benjamin', 'Calvin'];
 
$callback = function($user) use ($genius) {
    return $user == $genius;
};
 
$answer = array_filter($users, $callback);
 
echo implode('',$answer);
 
// Output:
// Addison

However, most of the people are not aware that we can do this:

class Person
{
    private $name;
 
    public function __construct($name)
    {
        $this->name = $name;
    }
 
    public function callMe()
    {
        return $this->name;
    }
 
}
 
$addison = new Person('Addison');
 
echo $addison->callMe();
// Output:
// Addison
 
$changePropertyClosure = function () {
    $this->name = 'Benjamin';
};
 
$doChangePropertyClosure = $changePropertyClosure->bindTo($addison, get_class($addison));
$doChangePropertyClosure();
 
echo $addison->callMe();
// Output:
// Benjamin

Pay attention to how Addison's name is changed by $changePropertyClosure to Benjamin, though the name is a private property of Person class.

The uncommon method of Closure class we have shown above is Closure::bindTo. It allows us to duplicate the closure with a new bound object and class scope. You can check out the method details at http://php.net/manual/en/closure.bindto.php.

In simple terms, we can use Closure's method bindTo to access private properties of any classes. With that in mind, let's move on to specific use cases.

Get Private Properties without Getter

Imagine we have an object which has getter methods to return private properties, but these are only used by the tests to check the state of the object and are never called, or intended to be called, by the client.

class Person
{
    private $name;
 
    public function __construct($name)
    {
        $this->name = $name;
    }
 
    public function hide()
    {
        $this->name = 'anonymous';
    }
 
    public function getName()
    {
        return $this->name;
    }
}

In the code above, we have a getter method getName(). Its sole purpose is to get the private property $name for out test. So that we can write a test for the hide() method.

By using Closure, we can actually eliminate the getName() method.

class PersonTest extends PHPUnit_Framework_TestCase
{
    public function testHide()
    {
        // Arrange
        $that = $this;
        $addison = new Person('Addison');
        $assertPropertyClosure = function ()  use ($that){
            $that->assertEquals('anonymous', $this->name);
        };
        $doAssertPropertyClosure = $assertPropertyClosure->bindTo($addison, get_class($addison));
 
        // Act
        $addison->hide();
 
        // Assert
        $doAssertPropertyClosure();
    }
}

As you can see from the code above. The Closure $assertPropertyClosure is able to look inside the Person object and get the private property $name using $this context.

Another nice trick we used is that since $this has been bound to the bound object, we just assigned it to a new variable $that, so that we can still use the assertion inside the Closure.

Set Mock without Setter

Imagine we have an object which has a setter method to set collaborator, but this setter is only used by the tests to set a mock of the collaborator, it is not used by the client.

class Phone
{
    function dial($number)
    {
 
    }
}
 
class Person
{
    private $phone;
 
    public function __construct()
    {
        $this->phone = new Phone;
    }
 
    public function call($number)
    {
        $this->phone->dial($number);
    }
 
    public function setPhone($phone)
    {
        $this->phone = $phone;
    }
 
}

In the code above, we have a setter method setPhone(). Its sole purpose is to set the private property $phone for out test. So that we can write a test for call() method.

By using Closure, we can actually eliminate the setPhone() method.

class PersonTest extends PHPUnit_Framework_TestCase
{
    public function testCall()
    {
        // Arrange
        $addison = new Person();
        $mockPhone = $this->getMockBuilder(Phone::class)->getMock();
        $mockPhone->expects($this->exactly(1))->method('dial');
        $setPhoneClosure = function ()  use ($mockPhone){
            $this->phone = $mockPhone;
        };
        $doSetPhoneClosure = $setPhoneClosure->bindTo($addison, get_class($addison));
        $doSetPhoneClosure();
 
        // Act
        $addison->call(93803212);
    }
}

As you can see from the code above. The Closure $setPhoneClosure is able to look inside the Person object and set the private property $phone using $this context.

The End

Hopefully this simple tutorial helped you with your development. If you like our post, please follow us on Twitter and help spread the word. We need your support to continue. If you have questions or find our mistakes in above tutorial, do leave a comment below to let us know.