3

Is there a way to unit-test protected or private methods of a class? As it is now, I'm making a lot of methods public in order to be able to test them, which breaks the API.

Edit: Actually answered here: Best practices to test protected methods with PHPUnit

Community
  • 1
  • 1
Olle Härstedt
  • 3,799
  • 1
  • 24
  • 57

2 Answers2

8

You can access your private and/or protected method by using the ReflectionMethod class followed by invoke method, but to invoke the method you also need an instance of your class which in certain situations isn't possible. Based on this one nice example that works is this one:

Get a mock of your class:

$mockedInstance = $this->getMockBuilder(YourClass::class)
        ->disableOriginalConstructor()    // you may need the constructor on integration tests only
        ->getMock();

Get your method to be tested:

$reflectedMethod = new \ReflectionMethod(
    YourClass::class,
    'yourMethod'
);

$reflectedMethod->setAccessible(true);

Call your private/protected method:

$reflectedMethod->invokeArgs(    //use invoke method if you don't have parameters on your method
    $mockedInstance, 
    [$param1, ..., $paramN]
);
Marcel Kohls
  • 1,650
  • 16
  • 24
  • 1
    Thanks for saving my day. It works like charm for me for private method. – Dhaval Mistry Sep 22 '21 at 09:39
  • 1
    Although, testing a private method is not a good practice. Just because the private methods in certain point will be called by a public method, and then, this public method should be the tested one. Anyway every situation is a different situation. :+1 – Marcel Kohls Sep 22 '21 at 15:04
  • Yes that make sense not to test private functions. – Dhaval Mistry Sep 23 '21 at 08:54
2

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/

malte
  • 1,439
  • 1
  • 11
  • 12
  • PSR-2 does not accept putting two classes in one file. So I'm not sure subclassing is a good alternative. Thanks for the tip with reflection. – Olle Härstedt Feb 07 '17 at 18:03
  • Absolutely right, used it in same 'file' for demonstration purposes only....In fact, using PHP 7 it would be a good use for an anonymous class... – malte Feb 07 '17 at 18:05