351

I found the discussion on Do you test private method informative.

I have decided, that in some classes, I want to have protected methods, but test them. Some of these methods are static and short. Because most of the public methods make use of them, I will probably be able to safely remove the tests later. But for starting with a TDD approach and avoid debugging, I really want to test them.

I thought of the following:

  • Method Object as adviced in an answer seems to be overkill for this.
  • Start with public methods and when code coverage is given by higher level tests, turn them protected and remove the tests.
  • Inherit a class with a testable interface making protected methods public

Which is best practice? Is there anything else?

It seems, that JUnit automatically changes protected methods to be public, but I did not have a deeper look at it. PHP does not allow this via reflection.

Community
  • 1
  • 1
GrGr
  • 4,098
  • 5
  • 21
  • 20
  • Two questions: 1. why should you bother testing functionality your class does not expose? 2. If you should test it, why it is private? – nad2000 Mar 20 '11 at 11:50
  • 5
    Maybe he wants to test if a private property is being set correctly and the only way of testing using only the setter function is to make the private property public and checking the data – AntonioCS Apr 23 '12 at 17:59
  • 3
    It's entirely possible that he may not want to test the public class interface as such, but the interface that it presents to child classes(which _can_ access protected methods) – robertmain Mar 14 '18 at 18:53
  • @lenswipe then he should unit test the child class' API, which will use the protected methods – Jeremy Belolo Apr 05 '18 at 07:52
  • @Jeremy Belolo and to do that, he would... Do what exactly? – robertmain Apr 07 '18 at 14:01
  • @robertmain: as he wrote, perhaps not directly clear: The API is to extend from the base class and then use the protected ones (otherwise there would have been no use of them anyway - for real cut it). The unit than can expose exactly that API view public methods etc. This would also show whether or not extending makes actually sense. Normally these tests and their stubs/mocks should be part of the main library if it is bound on protected visibility. If these unit tests are not available firsthand, how would you know that this is all only shit-baking on a protected level? – hakre Mar 15 '19 at 23:43
  • 1
    Except the protected methods are an implementation detail. What you're suggesting is testing the implementation not the interface. – robertmain Mar 17 '19 at 03:24

11 Answers11

505

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); // Use this if you are running PHP older than 8.1.0
  return $method;
}

public function testFoo() {
  $foo = self::getMethod('foo');
  $obj = new MyClass();
  $foo->invokeArgs($obj, array(...));
  ...
}
Dharman
  • 30,962
  • 25
  • 85
  • 135
uckelman
  • 25,298
  • 8
  • 64
  • 82
  • 4
    Very nice solution. Might want to add though that this is php >= 5.3.2 only ;) – flungabunga Oct 18 '10 at 01:57
  • 37
    To quote the link to sebastians blog: *"So: Just because the testing of protected and private attributes and methods is possible does not mean that this is a "good thing"."* - Just to keep that in mind – edorian Jun 08 '11 at 16:26
  • 22
    I would contest that. If you don't need your protected or private methods to work, don't test them. – uckelman Jun 09 '11 at 08:06
  • 17
    Just to clarify, you don't need to be using PHPUnit for this to work. It'll also work with SimpleTest or whatever. There's nothing about the answer that is dependent on PHPUnit. – Ian Dunn Aug 24 '11 at 04:57
  • 118
    You should not test protected/private members directly. They belong to the internal implementation of the class, and should not be coupled with the test. This makes refactoring impossible and eventually you don't test what needs to be tested. You need to test them indirectly using public methods. If you find this difficult, almost sure that there is a problem with the composition of the class and you need to separate it to smaller classes. Keep in mind that your class should be a black box for your test - you throw in something and you get something back, and that's all! – gphilip Feb 08 '12 at 15:48
  • 3
    Yes I think gphilip comment is the best answer. – Andrew Jun 09 '16 at 12:56
  • 47
    @gphilip To me, `protected` method is also a part of public api because **any third party class can extend it and use it** without any magic. So I think only `private` methods fall into the category of methods not to be directly tested. `protected` and `public` should be directly tested. – Filip Halaxa Jun 15 '17 at 06:01
  • 6
    @FilipHalaxa that's right, that's exactly why protected members are problematic, to say the least. Extensive use of them might signal poor design. For testability, code reuse and ease of understanding, prefer composition over inheritance and avoid fragile protected members when you can. – gphilip Jun 15 '17 at 13:45
  • 5
    In my opinion unit testing should be aware that some / many developers like me go for Test Driven Design. TDD is all-comprehensive, protected methods should be covered. Furthermore, I need to design simple, protected methods performing complex queries (even after having employed CRUD classes) and want to test them separately from the calling methods. – Dario Fumagalli Nov 22 '17 at 09:15
  • 4
    @gphilip Another issue with this logic is that unit tests help to not only indicate that "something is broken", but to help identify "what is broken". Imagine a class with one public method and ten protected methods. Having tests on key protected methods will likely make it much easier to quickly identify what is broken. (Although, agreed; such a scenario might be indicative of poor design) – rinogo Jan 29 '18 at 18:23
  • PHPUnit, for example, allows multiple testsuites. If your development is test driven, you can have a testsuite for general purpose and another one, (that may be added to `.gitignore`) for TDD, which may be deleted later. – Arcesilas Apr 13 '18 at 12:42
  • @Arcesilas ...why would you delete your TDD test suite? That makes no sense at all. – robertmain Apr 04 '19 at 15:22
  • 2
    Well, it's valuable when starting to work on an "inherited" codebase with an *ahem* interesting structure involving protected and static methods all over the place. So, the first step to clean up that mess is to add unit tests, and being able to do that on smaller units rather than the one and only public method that calls 20 protected methods (that call a further 5-10 protected methods inside them!) is more workable. – Juha Untinen Jun 18 '19 at 07:17
  • IMHO it makes a lot of sense to test protected methods of the classes that you currently work on (for TDD flow, when refactoring old code, etc.). – ivanhoe Aug 08 '19 at 19:36
  • 8
    I don't get people saying not to test private/protected functions just because. If your public method relies on 2 private methods that you just don't want to expose, wouldn't you test those private methods? Making everything public just for the sake of it is like chmoding every file in a server 777 just so you don't get a permission error. – Ben Sep 29 '19 at 18:49
  • @Ben I totally agree. If someone doesn't want to test a private function, why to actually create a function in first place? – Tom Raganowicz Feb 14 '20 at 22:04
  • @FilipHalaxa If method is public then it's part of the accessible API. If method is protected it can be extended. If method is `private` the API is they keyboard of the code owner. Those `private` methods can be used in their other `protected` or `public` methods which they rely on that `private method`. In all cases, do have your code tested.... or perhaps let's turn all methods to `private`, so you can save some time on testing, at least according to the best selling comment from @gphilip. – Tom Raganowicz Feb 14 '20 at 22:08
  • @edorian Much better than having no tests at all. – Tom Raganowicz Feb 14 '20 at 22:24
66

teastburn has the right approach. Even simpler is to call the method directly and return the answer:

class PHPUnitUtil
{
  public static function callMethod($obj, $name, array $args) {
        $class = new \ReflectionClass($obj);
        $method = $class->getMethod($name);
        // $method->setAccessible(true); // Use this if you are running PHP older than 8.1.0
        return $method->invokeArgs($obj, $args);
    }
}

You can call this simply in your tests by:

$returnVal = PHPUnitUtil::callMethod(
                $this->object,
                '_nameOfProtectedMethod', 
                array($arg1, $arg2)
             );
Dharman
  • 30,962
  • 25
  • 85
  • 135
robert.egginton
  • 769
  • 5
  • 3
60

You seem to be aware already, but I'll just restate it anyway; It's a bad sign, if you need to test protected methods. The aim of a unit test, is to test the interface of a class, and protected methods are implementation details. That said, there are cases where it makes sense. If you use inheritance, you can see a superclass as providing an interface for the subclass. So here, you would have to test the protected method (But never a private one). The solution to this, is to create a subclass for testing purpose, and use this to expose the methods. Eg.:

class Foo {
  protected function stuff() {
    // secret stuff, you want to test
  }
}

class SubFoo extends Foo {
  public function exposedStuff() {
    return $this->stuff();
  }
}

Note that you can always replace inheritance with composition. When testing code, it's usually a lot easier to deal with code that uses this pattern, so you may want to consider that option.

troelskn
  • 115,121
  • 27
  • 131
  • 155
  • 2
    You can just directly implement stuff() as public and return parent::stuff(). See my response. It seems I'm reading things too quickly today. – Michael Johnson Oct 31 '08 at 18:39
  • You're right; It's valid to change a protected method into a public one. – troelskn Nov 01 '08 at 17:34
  • So the code suggests my third option and "Note that you can always replace inheritance with composition." goes in the direction of my first option or http://www.refactoring.com/catalog/replaceInheritanceWithDelegation.html – GrGr Nov 03 '08 at 08:55
  • Yes to the first. How finely grained your objects should be is a matter of style. I generally create much smaller objects than most of my colleagues would. – troelskn Nov 03 '08 at 09:52
  • 42
    I don't agree that it is a bad sign. Let's make a difference between TDD and Unit Testing. Unit testing should test private methods imo, since these are units and would benefit just in the same way as unit testing public methods benefit from unit testing. – koen Dec 11 '09 at 19:56
  • I actually did the same as you and decided to idly look at what others are doing. Didn't even think of the method MJ suggested ala: public function protectedFoo() { parent::_protectedFoo() } implementation. – Aries Jan 24 '11 at 11:11
  • 41
    Protected methods *are* part of the interface of a class, they are not simply implementation details. The whole point of protected members are so that subclassers (users in their own right) can use those protected methods inside class exstions. Those clearly need to be tested. – B T Feb 13 '11 at 10:53
  • In PHP 7+ you can also use anonymous classes, like`$foo = new class extends Foo {// declare a public version of stuff()}`. – Alessandro Benoit Apr 12 '18 at 15:17
  • It looks like an unsolicited advise and does not answer the question. – Handsome Nerd May 30 '18 at 22:15
  • 5
    "The aim of a unit test, is to test the interface of a class..." Where did you get that conclusion from? In my opinion unit test is about testing the small unit of your code. Likely to be a function. Whether it's `private`, `protected` or `public` that's actually an implementation detail. Regardless of whether your unit is part of `public` interface or not, if it's tested it is protected from undesired changes which might break its logic. Have you ever had to fix a bug in a `private` method? If so, would the unit test help to avoid that bug? I can't believe people are debating this. – Tom Raganowicz Feb 14 '20 at 22:15
  • Why this is not marked as the solution? Subclassing is the cleanest way. – John Smith Apr 17 '22 at 21:24
26

I'd like to propose a slight variation to getMethod() defined in uckelman's answer.

This version changes getMethod() by removing hard-coded values and simplifying usage a little. I recommend adding it to your PHPUnitUtil class as in the example below or to your PHPUnit_Framework_TestCase-extending class (or, I suppose, globally to your PHPUnitUtil file).

Since MyClass is being instantiated anyways and ReflectionClass can take a string or an object...

class PHPUnitUtil {
    /**
     * Get a private or protected method for testing/documentation purposes.
     * How to use for MyClass->foo():
     *      $cls = new MyClass();
     *      $foo = PHPUnitUtil::getPrivateMethod($cls, 'foo');
     *      $foo->invoke($cls, $...);
     * @param object $obj The instantiated instance of your class
     * @param string $name The name of your private/protected method
     * @return ReflectionMethod The method you asked for
     */
    public static function getPrivateMethod($obj, $name) {
      $class = new ReflectionClass($obj);
      $method = $class->getMethod($name);
      // $method->setAccessible(true); // Use this if you are running PHP older than 8.1.0
      return $method;
    }
    // ... some other functions
}

I also created an alias function getProtectedMethod() to be explicit what is expected, but that one's up to you.

Dharman
  • 30,962
  • 25
  • 85
  • 135
teastburn
  • 2,938
  • 1
  • 20
  • 12
14

I think troelskn is close. I would do this instead:

class ClassToTest
{
   protected function testThisMethod()
   {
     // Implement stuff here
   }
}

Then, implement something like this:

class TestClassToTest extends ClassToTest
{
  public function testThisMethod()
  {
    return parent::testThisMethod();
  }
}

You then run your tests against TestClassToTest.

It should be possible to automatically generate such extension classes by parsing the code. I wouldn't be surprised if PHPUnit already offers such a mechanism (though I haven't checked).

Sliq
  • 15,937
  • 27
  • 110
  • 143
Michael Johnson
  • 2,287
  • 16
  • 21
  • Heh... it seems I'm saying, use your third option :) – Michael Johnson Oct 31 '08 at 18:37
  • 2
    Yes, that is exactly my third option. I am pretty sure, that PHPUnit does not offer such a mechanism. – GrGr Nov 03 '08 at 08:46
  • 1
    This won't work, you can't override a protected function with a public function with the same name. – Koen. Jul 15 '14 at 12:37
  • 1
    I might be wrong, but I don't think this approach can work. PHPUnit (as far as I've ever used it) requires that your test class extend another class that provides the actual testing functionality. Unless there's a way around that I'm not sure I can see how this answer can be used. https://phpunit.de/manual/current/en/phpunit-book.html#writing-tests-for-phpunit – Cypher Oct 07 '15 at 21:42
  • 2
    FYI this onl works for **protected** methods, not for private ones – Sliq Oct 09 '19 at 10:56
6

You can indeed use __call() in a generic fashion to access protected methods. To be able to test this class

class Example {
    protected function getMessage() {
        return 'hello';
    }
}

you create a subclass in ExampleTest.php:

class ExampleExposed extends Example {
    public function __call($method, array $args = array()) {
        if (!method_exists($this, $method))
            throw new BadMethodCallException("method '$method' does not exist");
        return call_user_func_array(array($this, $method), $args);
    }
}

Note that the __call() method does not reference the class in any way so you can copy the above for each class with protected methods you want to test and just change the class declaration. You may be able to place this function in a common base class, but I haven't tried it.

Now the test case itself only differs in where you construct the object to be tested, swapping in ExampleExposed for Example.

class ExampleTest extends PHPUnit_Framework_TestCase {
    function testGetMessage() {
        $fixture = new ExampleExposed();
        self::assertEquals('hello', $fixture->getMessage());
    }
}

I believe PHP 5.3 allows you to use reflection to change the accessibility of methods directly, but I assume you'd have to do so for each method individually.

David Harkness
  • 35,992
  • 10
  • 112
  • 134
  • 1
    The __call() implementation works great! I tried to vote up, but I unset my vote until after I tested this method and now I'm not allowed to vote due to a time limit in SO. – Adam Franco Sep 14 '10 at 18:25
  • The `call_user_method_array()` function is deprecated as of PHP 4.1.0 ... use `call_user_func_array(array($this, $method), $args)` instead. Note that if you are using PHP 5.3.2+ you can use Reflection to [gain access to protected/private methods and attributes](http://sebastian-bergmann.de/archives/881-Testing-Your-Privates.html) – nuqqsa May 25 '11 at 11:58
  • @nuqqsa - Thanks, I updated my answer. I have since written a generic `Accessible` package that uses reflection to allow tests to access private/protected properties and methods of classes and objects. – David Harkness May 25 '11 at 18:24
  • This code doesn't work for me on PHP 5.2.7 -- the __call method does not get invoked for methods that the base class defines. I can't find it documented, but I'm guessing this behavior was changed in PHP 5.3 (where I've confirmed it works). – Russell Davis Jun 23 '11 at 01:22
  • @Russell - `__call()` only gets invoked if the caller does not have access to the method. Since the class and its subclasses have access to the protected methods, calls to them won't go through `__call()`. Can you post your code that doesn't work in 5.2.7 in a new question? I used the above in 5.2 and only moved to using reflection with 5.3.2. – David Harkness Jun 23 '11 at 02:28
  • @David, I used the exact code above (though I had to fix a typo: `protected getMessage()` --> `protected function getMessage()`). It results in `Fatal error: Call to protected method Example::getMessage() from context ''` – Russell Davis Jun 23 '11 at 07:05
  • Update: It works on PHP 5.2.17. The behavior changed somewhere between 5.2.7 and 5.2.17. – Russell Davis Jun 23 '11 at 08:15
  • @Russell - Thanks, that narrows it down a bit. I don't remember what 5.2.x version we were using when I wrote the above. And yes, if I haven't needed to type `$` in a while, I fall back to writing Java code and drop `function` from all my methods. :) – David Harkness Jun 23 '11 at 17:17
6

I'm going to throw my hat into the ring here:

I've used the __call hack with mixed degrees of success. The alternative I came up with was to use the Visitor pattern:

1: generate a stdClass or custom class (to enforce type)

2: prime that with the required method and arguments

3: ensure that your SUT has an acceptVisitor method which will execute the method with the arguments specified in the visiting class

4: inject it into the class you wish to test

5: SUT injects the result of operation into the visitor

6: apply your test conditions to the Visitor's result attribute

sunwukung
  • 2,815
  • 3
  • 41
  • 56
2

I suggest following workaround for "Henrik Paul"'s workaround/idea :)

You know names of private methods of your class. For example they are like _add(), _edit(), _delete() etc.

Hence when you want to test it from aspect of unit-testing, just call private methods by prefixing and/or suffixing some common word (for example _addPhpunit) so that when __call() method is called (since method _addPhpunit() doesn't exist) of owner class, you just put necessary code in __call() method to remove prefixed/suffixed word/s (Phpunit) and then to call that deduced private method from there. This is another good use of magic methods.

Try it out.

1

Alternative.The code below is provided as an example. Its implementation can be much broader. Its implementation that will help you test private methods and replacing a private property .

    <?php
    class Helper{
        public static function sandbox(\Closure $call,$target,?string $slaveClass=null,...$args)
        {
            $slaveClass=!empty($slaveClass)?$slaveClass:(is_string($target)?$target:get_class($target));
            $target=!is_string($target)?$target:null;
            $call=$call->bindTo($target,$slaveClass);
            return $call(...$args);
        }
    }
    class A{
        private $prop='bay';
        public function get()
        {
            return $this->prop;    
        }
        
    }
    class B extends A{}
    $b=new B;
    $priv_prop=Helper::sandbox(function(...$args){
        return $this->prop;
    },$b,A::class);
    
    var_dump($priv_prop);// bay
    
    Helper::sandbox(function(...$args){
        $this->prop=$args[0];
    },$b,A::class,'hello');
    var_dump($b->get());// hello
AlexeyP0708
  • 307
  • 2
  • 5
1

You can use Closure as in the code below

<?php

class A
{
    private string $value = 'Kolobol';
    private string $otherPrivateValue = 'I\'m very private, like a some kind of password!';

    public function setValue(string $value): void
    {
        $this->value = $value;
    }

    private function getValue(): string
    {
        return $this->value . ': ' . $this->getVeryPrivate();
    }

    private function getVeryPrivate()
    {
        return $this->otherPrivateValue;
    }
}

$getPrivateProperty = function &(string $propName) {
    return $this->$propName;
};

$getPrivateMethod = function (string $methodName) {
    return Closure::fromCallable([$this, $methodName]);
};

$objA = new A;
$getPrivateProperty = Closure::bind($getPrivateProperty, $objA, $objA);
$getPrivateMethod = Closure::bind($getPrivateMethod, $objA, $objA);
$privateByLink = &$getPrivateProperty('value');
$privateMethod = $getPrivateMethod('getValue');

echo $privateByLink, PHP_EOL; // Kolobok

$objA->setValue('Zmey-Gorynich');
echo $privateByLink, PHP_EOL; // Zmey-Gorynich

$privateByLink = 'Alyonushka';
echo $privateMethod(); // Alyonushka: I'm very private, like a some kind of password!
1

I made a class for invoking easily private methods (static and non-static) for unit-testing purposes:

class MethodInvoker
{
    public function invoke($object, string $methodName, array $args=[]) {
        $privateMethod = $this->getMethod(get_class($object), $methodName);

        return $privateMethod->invokeArgs($object, $args);
    }

    private function getMethod(string $className, string $methodName) {
        $class = new \ReflectionClass($className);
        
        $method = $class->getMethod($methodName);
        $method->setAccessible(true);
    
        return $method;
    }
}

Example of usage:

class TestClass {
    private function privateMethod(string $txt) {
        print_r('invoked privateMethod: ' . $txt);
    }
}

(new MethodInvoker)->invoke(new TestClass, 'privateMethod', ['argument_1']);
Dan
  • 3,329
  • 2
  • 21
  • 28