87

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

David Harkness
  • 35,992
  • 10
  • 112
  • 134
Tony
  • 3,425
  • 10
  • 30
  • 46

11 Answers11

105

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.

edorian
  • 38,542
  • 15
  • 125
  • 143
  • 1
    this is not true, in my example i need to test the second 'some code...' but that result maybe altered by the previous result of the private function that i can't mock, here's the example : public function b() { // some code if($this->c() == 0) // do something; else // do someanotherthing // some code } – Tony May 09 '11 at 14:03
  • 1
    @dyoser: If you really need to mock a private method, you implicitly suggest, that the result may change in a way you cannot control via accessable methods. Or: If the test-case (blackbox- or greybox-tests) needs to know something about the internal structure of the unit to test, something is wrong in the unit. – KingCrunch May 09 '11 at 14:35
  • @dyoser Sure it may be altered by what the private function does, thats why you usually need more than one test case. Every possible effect that function c() has will be triggered by you passing in some parameters to your class so if you pass in the right stuff c() also will do the right stuff. Anyways, I'll build a little example where you might want to mock c() – edorian May 10 '11 at 08:07
  • this is what it shows when i do that thing => PHP Fatal error: Call to private method Mock_ATest_a3323606::c() from context... – Tony May 10 '11 at 09:54
  • 2
    Yeah thats what i meant with the "// maybe set the function protected for this to work" comment. Ether change the function to protected or use reflection to change it – edorian May 10 '11 at 11:35
  • How could I test the public function properties ($this->variable) without a return? – nova Sep 26 '16 at 13:50
  • @edorian Why is the *"Some more: **`Why you don't want test private methods.`**"* link pointing to this question itself? Doesn't seem to be useful. Is that a mistake? – Pang Nov 09 '16 at 06:55
  • Solution downvoted for requiring extensive refactoring of code under test (as in doubling the number of classes in this particular example). – Szczepan Hołyszewski Dec 24 '16 at 06:29
  • 7
    "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." Should not doesn't mean it won't break, in which case I would like my unit test to indicate which of the private methods could be responsible for the failure. If I test the public interface only which is dependent on many smaller units, isn't it effectively an integration test? I think testing world went into weird corporate rules dictated by test coverage in which case testing public methods helps a lot to build nice stats. – Tom Raganowicz Feb 14 '20 at 22:21
  • @NeverEndingQueue Nice argument. In addition to that not breaking public API doesn't mean that, everything is alright or that your class design is ok or that one day your tests will not fail for apparent no reason at all, which otherwise could be spotted early by unit testing private methods. For example, imagine some memory leaks, which you can detect only by inspecting allocated resources state from executing private methods. – Agnius Vasiliauskas Feb 04 '21 at 09:26
  • The question asks "How to", not "should I". Legitimity is another debate. You can for example comment about legitimity, but reserve answers for actual answers. If people could take the time to understand a question before answering it, the net would be a better place. – Moonchild Apr 01 '21 at 13:26
  • A use case where you want to set private properties: You have an object with dateFrom and dateTo and a isActive() method that returns "Expired" if both are in the past. But the initial class validation throws exceptions if you create the class with a date in the past. So the only way to test the class would be to either wait or directly write dateFrom and dateTo – Sebus Dec 22 '22 at 12:45
28

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 ...
David Harkness
  • 35,992
  • 10
  • 112
  • 134
  • 4
    Nope, you're using reflection on a property but in the example y use a private METHOD. Just to clarify i want to simulate (mock) the running of the private method to obtain the result that i want (or just a simulated one). I don't need to test it. – Tony May 10 '11 at 07:58
  • 1
    @dyoser - If you don't want to alter the class under test, you must alter its state such that the private method operates as you need. Since the public method can call the private method, and you cannot override the private method, you cannot mock it. – David Harkness May 10 '11 at 08:47
  • 1
    Solved a different problem for me, but works as a charm. – Kick_the_BUCKET Apr 02 '14 at 14:32
27

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.

rybo111
  • 12,240
  • 4
  • 61
  • 70
doctore
  • 3,855
  • 2
  • 29
  • 45
18

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.

Archit Rastogi
  • 195
  • 1
  • 8
  • 1
    Its really works. Here is more complete example: https://phpunit.de/manual/current/en/test-doubles.html#test-doubles.mock-objects.examples.SubjectTest.php – Nick Jan 16 '17 at 14:05
  • 1
    You shouldn't change things to invalid settings like making a private method protected just to support testing. If testing messes up your code then it's wrong. – jgmjgm Mar 19 '19 at 12:59
15

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);
Edson Medina
  • 9,862
  • 3
  • 40
  • 51
  • 1
    Hi @Edson, your answer is the same that I said with the link: "test private methods". – doctore Aug 19 '11 at 08:10
  • 1
    The OP doesn't need to *test* the private method; they need to *mock* it to provide a canned result while testing a *different* method. – David Harkness Mar 11 '14 at 03:07
  • @DavidHarkness I agree with you that private methods should not be tested, but it's debatable. It's a shady area. If you want to go anal with the coverage there's no pretty away around it. – Edson Medina Mar 11 '14 at 11:20
  • @EdsonMedina While I did say "doesn't need to test," I didn't mean to imply the OP shouldn't test the private method--only that they didn't want to for the purpose of this question. I just noticed that the title doesn't match the body at all and will fix that now. – David Harkness Mar 11 '14 at 21:22
  • @DavidHarkness It's a little too late to change the question 3 years after getting responses and closing the thread, don't you think? That will make it confusing. – Edson Medina Mar 12 '14 at 10:59
  • I think it's warranted in this case since so many other pages link to it as "mocking a private method." – David Harkness Mar 12 '14 at 16:28
10

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);
}
Mark McEver
  • 143
  • 1
  • 4
10

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.

Asaph
  • 159,146
  • 25
  • 197
  • 199
  • sure but this means that i need to rewrite the original class, it's not a good implementation to test functionality. – Tony May 09 '11 at 14:00
  • 6
    @dyoser: Yes, this is a necessary thing. Your original implementation is somewhat untestable and needs to be refactored a little to get it under test. Don't be afraid to do it. Michael Feathers talks about this very issue in his excellent book [Working Effectively with Legacy Code](http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052/). – Asaph May 09 '11 at 14:02
8

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.

Torge
  • 2,174
  • 1
  • 23
  • 33
  • This is actually quite useful, thanks! Do you have this on [packagist](https://packagist.org) or some repository that could be included with [composer](https://getcomposer.org/)? I did find [eloquent/liberator](https://github.com/eloquent/liberator) by the namesake, but I think it's just a coincidence. – Jeff Puckett Sep 09 '16 at 13:53
  • Thanks! No I have not. This is a coincidence. This code is selfcontained so feel free to change the class name if it gives you collisions. – Torge Sep 09 '16 at 13:59
1

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

Thellimist
  • 3,757
  • 5
  • 31
  • 49
0

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.

jgmjgm
  • 4,240
  • 1
  • 25
  • 18
0

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.

Moonchild
  • 1,382
  • 10
  • 15