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.
Table Of Content
1. 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.
However, most of the people are not aware that we can do this:
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.
2. 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.
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.
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.
3. 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.
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.
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.
4. 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.