Mock private method with PHPUnit

PhpMockingPhpunit

Php Problem Overview


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

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

  private function c(){ 
    // some code
  }
}

How can I stub the result of the private method to test the some more code part of the public function.

Solved partially reading here

Php Solutions


Solution 1 - Php

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.

Solution 2 - Php

You can test private methods but you can't simulate (mock) the running of this methods.

Furthermore, the reflection does not allow you to convert a private method to a protected or public method. setAccessible only allows you to invoke the original method.

Alternatively, you could use runkit for rename the private methods and include a "new implementation". However, these features are experimental and their use is not recommended.

Solution 3 - Php

You can use reflection and setAccessible() in your tests to allow you to set the internal state of your object in such a way that it will return what you want from the private method. You'll need to be on PHP 5.3.2.

$fixture = new MyClass(...);
$reflector = new ReflectionProperty('MyClass', 'myPrivateProperty');
$reflector->setAccessible(true);
$reflector->setValue($fixture, 'value');
// test $fixture ...

Solution 4 - Php

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

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

This will definitely work , It worked for me . Then For covering protected method C you can use reflection classes.

Solution 5 - Php

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

$class = new ReflectionClass ($myClass);
$method = $class->getMethod ('privateMethodX');
$method->setAccessible(true);
$output = $method->invoke ($myClass, $arg1, $arg2);

Solution 6 - Php

Here's a variation of the other answers that can be used to make such calls 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);
}

Solution 7 - Php

One option would be to make c() protected instead of private and then subclass and override c(). Then test with your subclass. Another option would be to refactor c() out into a different class that you can inject into A (this is called dependency injection). And then inject a testing instance with a mock implementation of c() in your unit test.

Solution 8 - Php

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 & transparently liberate 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 the properties/methods called in the Liberator class.

Solution 9 - Php

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

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

$response = $myMockObject->myPublicMethod();

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

Solution 10 - Php

You can use anonymous classes using PHP 7.

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

In prior versions of PHP you can make a test class extending the base class.

Solution 11 - Php

When the target class is neither static nor final, I solve this kind of problem in a way close to @jgmjgm, by anonymously and locally extending the original class and therefore not testing the same method multiple times in different test cases :

$extent = new class ($args) extends A {
    private function c() { 
        return $hardCodedValue;
    }
};

Then I can test the behaviour of $extent->b() without executing again the potentially heavy c() method.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTonyView Question on Stackoverflow
Solution 1 - PhpedorianView Answer on Stackoverflow
Solution 2 - PhpdoctoreView Answer on Stackoverflow
Solution 3 - PhpDavid HarknessView Answer on Stackoverflow
Solution 4 - PhpArchit RastogiView Answer on Stackoverflow
Solution 5 - PhpEdson MedinaView Answer on Stackoverflow
Solution 6 - PhpMark McEverView Answer on Stackoverflow
Solution 7 - PhpAsaphView Answer on Stackoverflow
Solution 8 - PhpTorgeView Answer on Stackoverflow
Solution 9 - PhpThellimistView Answer on Stackoverflow
Solution 10 - PhpjgmjgmView Answer on Stackoverflow
Solution 11 - PhpMoonchildView Answer on Stackoverflow