Layout of a private method using PHPUnit

I have a question about using PHPUnit to mock a private method inside a class. Let me give an example:

class A { public function b() { // some code $this->c(); // some more code } private function c(){ // some code } } 

How can I drown out the result of a private method to test another piece of public function code.

Decided to partially read here

+65
php mocking phpunit
May 09 '11 at 13:53
source share
10 answers

Usually you just don’t check or scoff at directy’s personal and protected methods.

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.

It also helps you when you notice that β€œyou cannot get 100% coverage of the code,” because you may have code in your class that you cannot execute by calling the open API.




Usually you do not 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 see the need to want to mock c (), since the "random" function is a global state, and you cannot check it.

"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 you no longer have to mock "c ()", since it does not contain any globals, and you can test them well.




If you do not want to do or cannot remove the global state from your private function (bad reality or the definition of bad can be different), you can check for mockery.

 // 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 layout as the only function you take out / mock is "c ()".




To quote the book Pragmatic Unit Testing:

β€œIn general, you don’t want to break encapsulation for the sake of testing (or, as your mother said,β€œ do not expose your privates! ”). In most cases, you should be able to test the class using your public methods. If there is significant functionality that is hidden behind with private or secure access, this may be a warning sign that another class is trying to exit there. "




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

+80
May 09 '11 at 13:57
source share
β€” -

You can test private methods, but you cannot imitate (mock) the execution of these methods.

In addition, reflection does not allow you to convert a private method into a protected or public method. setAccessible only allows you to call the original method.

Alternatively, you can use runkit to rename private methods and enable the "new implementation". However, these functions are experimental, and their use is not recommended.

+25
May 10 '11 at 15:42
source share

You can use reflection and setAccessible() in your tests so that you can set the internal state of your object so that it returns what you want from a private method. You need to be in PHP 5.3.2.

 $fixture = new MyClass(...); $reflector = new ReflectionProperty('MyClass', 'myPrivateProperty'); $reflector->setAccessible(true); $reflector->setValue($fixture, 'value'); // test $fixture ... 
+24
May 10 '11 at 1:07
source share

You can get a mock protected method, so if you can convert C to secure, this code will help.

  $mock = $this->getMockBuilder('A') ->disableOriginalConstructor() ->setMethods(array('C')) ->getMock(); $response = $mock->B(); 

It will definitely work, it worked for me. Then, to cover the protected method C, you can use reflection classes.

+12
Aug 08 '16 at 1:46
source share

Assuming you need to check $ myClass-> privateMethodX ($ arg1, $ arg2), you can do this with a reflection:

 $class = new ReflectionClass ($myClass); $method = $class->getMethod ('privateMethodX'); $method->setAccessible(true); $output = $method->invoke ($myClass, $arg1, $arg2); 
+10
Aug 17 2018-11-11T00:
source share

Here are options for other answers that can be used to make such calls in one line:

 public function callPrivateMethod($object, $methodName) { $reflectionClass = new \ReflectionClass($object); $reflectionMethod = $reflectionClass->getMethod($methodName); $reflectionMethod->setAccessible(true); $params = array_slice(func_get_args(), 2); //get all the parameters after $methodName return $reflectionMethod->invokeArgs($object, $params); } 
+9
Aug 02 '13 at 15:25
source share

I came up with this general purpose class for my case:

 /** * @author Torge Kummerow */ class Liberator { private $originalObject; private $class; public function __construct($originalObject) { $this->originalObject = $originalObject; $this->class = new ReflectionClass($originalObject); } public function __get($name) { $property = $this->class->getProperty($name); $property->setAccessible(true); return $property->getValue($this->originalObject); } public function __set($name, $value) { $property = $this->class->getProperty($name); $property->setAccessible(true); $property->setValue($this->originalObject, $value); } public function __call($name, $args) { $method = $this->class->getMethod($name); $method->setAccessible(true); return $method->invokeArgs($this->originalObject, $args); } } 

With this class, you can now easily and transparently release all private functions / fields on any object.

 $myObject = new Liberator(new MyObject()); /* @var $myObject MyObject */ //Usefull for code completion in some IDEs //Writing to a private field $myObject->somePrivateField = "testData"; //Reading a private field echo $myObject->somePrivateField; //calling a private function $result = $myObject->somePrivateFunction($arg1, $arg2); 

If performance is important, it can be improved by caching properties / methods called in the Liberator class.

+6
Feb 19 '16 at 13:09
source share

One option is to make c() protected instead of private , and then subclass and override c() . Then check your subclass. Another option is to reorganize c() into another class, which you can introduce in A (this is called dependency injection). Then add a test instance with a mock implementation of c() in the unit test.

+5
May 9 '11 at 1:58 p.m.
source share

An alternative solution is to change your private method to a protected method, and then a layout.

 $myMockObject = $this->getMockBuilder('MyMockClass') ->setMethods(array('__construct')) ->setConstructorArgs(array("someValue", 5)) ->setMethods(array('myProtectedMethod')) ->getMock(); $response = $myMockObject->myPublicMethod(); 

where myPublicMethod calls myProtectedMethod . Unfortunately, we cannot do this with private methods, since setMethods cannot find a private method, where it can find a protected method

+2
Jul 19 '16 at 13:10
source share

You can use anonymous classes using PHP 7.

 $mock = new class Concrete { private function bob():void { } }; 

In previous versions of PHP, you could create a test class that extends the base class.

0
Jun 13 '19 at 17:44
source share



All Articles