Best practices to test protected methods with PHPUnit

前端 未结 8 904
情深已故
情深已故 2020-11-28 17:14

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 thes

相关标签:
8条回答
  • 2020-11-28 17:35

    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.

    0 讨论(0)
  • 2020-11-28 17:37

    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);
            return $method->invokeArgs($obj, $args);
        }
    }
    

    You can call this simply in your tests by:

    $returnVal = PHPUnitUtil::callMethod(
                    $this->object,
                    '_nameOfProtectedMethod', 
                    array($arg1, $arg2)
                 );
    
    0 讨论(0)
  • 2020-11-28 17:37

    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.

    0 讨论(0)
  • 2020-11-28 17:41

    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(...));
      ...
    }
    
    0 讨论(0)
  • 2020-11-28 17:44

    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);
          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.

    0 讨论(0)
  • 2020-11-28 17:51

    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).

    0 讨论(0)
提交回复
热议问题