Best Practices to Test Protected Methods with PHPunit

Best practices to test protected methods with PHPUnit

If you're using PHP5 (>= 5.3.2) with PHPUnit, you can test your private and protected methods by using reflection to set them to be public prior to running your tests:

protected static function getMethod($name) {
$class = new ReflectionClass('MyClass');
$method = $class->getMethod($name);
$method->setAccessible(true);
return $method;
}

public function testFoo() {
$foo = self::getMethod('foo');
$obj = new MyClass();
$foo->invokeArgs($obj, array(...));
...
}

How to unit-test protected methods?

For protected methods, you can subclass the class under test:

class Foo 
{
protected function doThings($foo)
{
//...
}
}

class _Foo extends Foo
{
public function _doThings($foo)
{
return $this->doThings($foo);
}
}

and in the test:

$sut = new _Foo();
$this->assertEquals($expected, $sut->_doThings($stuff));

With private methods it is a bit more difficult, you could use the Reflection API to call protected methods. Also, there is an argument that private methods should only come into existence during refactoring so should be covered by the public methods that call them, but that only really works if you did test-first to start with and in real life we have legacy code to deal with ;)

Links for the reflection api:

http://php.net/manual/en/reflectionmethod.setaccessible.php

Also, this link looks useful for this purpose:

https://jtreminio.com/2013/03/unit-testing-tutorial-part-3-testing-protected-private-methods-coverage-reports-and-crap/

PHPUnit testing a protected method that calls a private method which needs to be mocked

$result = $myProtectedMethod->invoke($reflectionClass, $mockArgOne, $mockArgTwo);

should be

$result = $myProtectedMethod->invoke($mmyMockClass, $mockArgOne, $mockArgTwo);

For more information how to use "invoke" method here. https://www.php.net/manual/en/reflectionmethod.invoke.php

Mock private method with PHPUnit

Usually you just don't test or mock the private & protected methods directy.

What you want to test is the public API of your class. Everything else is an implementation detail for your class and should not "break" your tests if you change it.

That also helps you when you notice that you "can't get 100% code coverage" because you might have code in your class that you can't execute by calling the public API.


You usually don't want to do this

But if your class looks like this:

class a {

public function b() {
return 5 + $this->c();
}

private function c() {
return mt_rand(1,3);
}
}

i can see the need to want to mock out c() since the "random" function is global state and you can't test that.

The "clean?/verbose?/overcomplicated-maybe?/i-like-it-usually" Solution

class a {

public function __construct(RandomGenerator $foo) {
$this->foo = $foo;
}

public function b() {
return 5 + $this->c();
}

private function c() {
return $this->foo->rand(1,3);
}
}

now there is no more need to mock "c()" out since it does not contain any globals and you can test nicely.


If you don't want to do or can't remove the global state from your private function (bad thing bad reality or you definition of bad might be different) that you can test against the mock.

// maybe set the function protected for this to work
$testMe = $this->getMock("a", array("c"));
$testMe->expects($this->once())->method("c")->will($this->returnValue(123123));

and run your tests against this mock since the only function you take out/mock is "c()".


To quote the "Pragmatic Unit Testing" book:

"In general, you don't want to break any encapsulation for the sake of testing (or as Mom used to say, "don't expose your privates!"). Most of the time, you should be able to test a class by exercising its public methods. If there is significant functionality that is hidden behind private or protected access, that might be a warning sign that there's another class in there struggling to get out."


Some more: Why you don't want test private methods.



Related Topics



Leave a reply



Submit