PHP Testing your privates
15th March, 2017
I was recently writing some tests for a PHP object that had some internal state I wanted to check.
As I was building the class with TDD it was natural for me to test the state by
adding getter
s to the object, so I could assemble my object, run the methods I
wanted to test, then assert the resulting state of the object.
<?php
class FooTest extends PHPUnit\Framework\TestCase
{
function test_it_does_something_internally()
{
$foo = new Foo;
$foo->doSomething();
$this->assertEquals('modified', $foo->getProperty());
}
}
class Foo
{
private $property;
public function doSomething()
{
// Some other code...
$this->property = 'modified';
}
public function getProperty()
{
return $this->property;
}
}
This all looked good, but once I had finished writing my tests and code I
realised that my tests were the only places I was using the getProperty()
method. I had methods in my object that were only being used in my
test. This didn't feel quite
right. I particularly didn't want clients of this object to be able to get at
the private property (which was a reference to another object in this case) and
manipulate that directly, I wanted other developers to interact with that object
through this Façade class. But how did I communicate that intent?
A few options, some better than others, came to mind:
1) Drop the public getter
method on the class, and use reflection in the test
to check the property.
<?php
class FooTest extends PHPUnit\Framework\TestCase
{
function test_it_does_something_internally()
{
$foo = new Foo;
$foo->doSomething();
$property = (new ReflectionObject($foo))->getProperty('property');
$property->setAccessible('true');
$this->assertEquals('modified', $property->getValue($foo));
}
}
class Foo
{
private $property;
public function doSomething()
{
// Some other code...
$this->property = 'modified';
}
}
It does clean up the source code by removing the method, but now the test seems a bit clunky.
2) Don't have the public getter
in the class we are testing, but make a test
class (or anonymous class if using PHP7!) that extends the base class and add
the getter
method in there.
<?php
class FooTest extends PHPUnit\Framework\TestCase
{
function test_it_does_something_internally()
{
$foo = new Class extends Foo
{
public function getProperty()
{
return $this->property;
}
};
$foo->doSomething();
$this->assertEquals('modified', $foo->getProperty());
}
}
class Foo
{
protected $property;
public function doSomething()
{
// Some other code...
$this->property = 'modified';
}
}
Note that in order for the child test class to be able to access the property I
would have to declare the visibility as protected
and not private
. Also this
would not work if the class I was testing was declared as final
, as I would
not be able to extend it.
3) Leave the getter
method there, but mark it as @internal
- so if other
developers tried to use it in the future, and if they are using an IDE like
PHPStorm then it would be displayed with a ~~strikethrough~~.
This is a bit unconventional, and the other developers could still use the
method as it is part of the classes public API, but if everyone on the team knew
not to use @internal
methods then this could be the easiest option.
4) Use an integration test, stop unit testing this bit. The reason there were no
other needs to use the getter
in the rest of the code base was because the
private property was mapped by an ORM (Doctrine in this case) to the database. I
could have made my test more of an integration test like so:
<?php
class FooTest extends PHPUnit\Framework\TestCase
{
// the entity manager would have to be constructed or passed somewhere else
private $em;
function test_it_does_something_internally()
{
$foo = new Foo;
$foo->doSomething();
$this->em->save($foo);
$dbResult = // insert SQL query here: SELECT `property` FROM `table` WHERE
$this->assertEquals('modified', $dbResult['property']);
}
}
class Foo
{
private $property;
public function doSomething()
{
// Some other code...
$this->property = 'modified';
}
}
The plus side of this is I'm testing something closer to the real code, but
that's about the only benefit. The cons are that it requires a database
connection to run, so it will be a little bit slower, and there is that ugly,
brittle SQL query in there to fetch the data too. I guess I could use an
interface for the repository saving the element, and just save it to memory, but
then I'd still need to add a public getter
to view the property or reflection
again, so I've not really gained anything!
5) Then last week I read this great blog post from Mark Baker. He explains that you can bind closures to another object, and grant them the scope of that object - meaning you can access private properties/methods once your closure is bound to a particular class. It deserves a demonstration:
<?php
class FooTest extends PHPUnit\Framework\TestCase
{
function test_it_does_something_internally()
{
$foo = new Foo;
$foo->doSomething();
$getProperty = function()
{
return $this->property;
};
$getPropertyOfFoo = $getProperty->bindTo($foo, $foo);
$this->assertEquals('modified', $getPropertyOfFoo());
}
}
class Foo
{
private $property;
public function doSomething()
{
// Some other code...
$this->property = 'modified';
}
}
To anyone who's not bound a closure to another object before this may look a
little strange at first. We have effectively moved the public getter
from our
tested class, into a closure $getProperty
. We then bindTo($foo, $foo)
which
effectively means that $this
refers to our instance of $foo
in the closure.
We then call the closure in $getPropertyOfFoo()
. I think this does muddle the
test a little bit so I extracted it out into a private method, which meant I
could call it multiple times in my test case too if I needed.
<?php
class FooTest extends PHPUnit\Framework\TestCase
{
function test_it_does_something_internally()
{
$foo = new Foo;
$foo->doSomething();
$this->assertEquals('modified', $this->getPropertyOf($foo));
}
private function getPropertyOf($obj)
{
return (function() {
return $this->property;
})->bindTo($obj, $obj)();
}
}
class Foo
{
private $property;
public function doSomething()
{
// Some other code...
$this->property = 'modified';
}
}
I don't think this should be used everywhere as generally I prefer tests that test behaviour rather than state, but this pattern could come in handy when you feel your test behaviour leaking into your domain model.
Have you ever been in a similar scenario as I describe? How did you solve that? I'd be interested to hear!
Further reading
Category: testing