Unit Testing Private Methods

Unit Testing Private Methods

Yes I Know

“A private method is an implementation detail”. “A private method should be hidden to the users of the class”. “Testing private methods breaks encapsulation”.

Yes, I’ve heard all of these statements, and for the most part, I would agree. But not always!

Yes, the private method is an implementation, but it’s an implementation that does work. If written well it may be utilized by more than one of its sibling methods, maybe in many different ways. If the method is protected it may get used elsewhere. I need a guarantee that it works!

In the end, this is all a learning experience. Whether I should do it or not, I still want to know how to do it.

Mirror, Mirror.

As always the first thing I do when I need an answer is to Google it. I found a lot of “You shouldn’t do this” and a few “Here’s my hacks”. But nothing that really met my requirements. Simple, lite, and reusable. So, time to build it myself! Besides, I can always use the practice.

Of the hacks I found, most fell into three categories…

  • Exstend the class and access the method through a wrapper.
  • Changing the visibility of the method and make it public.
  • Create a Proxy and Reflect the class.

I don’t like the idea of extending the class, that is just a lot of bloat coding. Besides, in most cases, this will not work unless the method is protected. Private methods are just that, private to the class. Changing the visibility was also a no-go, as the method being accessed may belong to another developer or a third-party library. I don’t want to get into maintaining a bunch of hacks that I need to do every time I run composer update. Aside from that, the method is private for a reason. It should stay that way.

I decided Reflection seemed to be the cleanest of the bunch. Now to simplify the usages I found and make it reusable for my needs.

The Development.

Reflection is an underused, sometimes understood, and definitely under-documented addition to PHP 5. What it does is allow the developer to reverse engineer existing classes, interfaces, functions, methods, and extensions. Exposing every detail to be manipulated on the fly at runtime.

In this implementation, we are going to concern ourselves with the ReflectionClass. There are other parts of the Reflection library that could give us the same result but for now, we’ll focus on this manner. I may visit those other routes in a future post.

Let’s break it all down. The Reflection technique of accessing the private method consists of four basic steps.

  • Create a Reflection of the class.
  • Get the method.
  • Change the visbility of the method.
  • Invoke the method.

Simple enough. Let’s start by mapping out a function to do our work.

Any method, public or private, can be broken down into three ingredients. The class object, the method name, and any parameters that are passed into it. The parameters being the only optional ingredient. Lets set that up.

/**
  * Call protected/private method of a class statically.
  *
  * @param $object
  * @param $methodName
  * @param array $parameters
  *
  * @return mixed
  */
function invoke($object, $methodName, array $parameters = array())
{

}

Now let’s work out exactly what steps we need to take to replicate our private method. I am assuming that we have an object already instantiated. This is usually the case in a testing class.

We’ll need to get the object’s class name which we can easily do with PHP’s get_class() function. Once we have that we can pass it into the ReflectionClass constructor which will give us back our shiny new reflection of the original object (Line 13 below). Now that we have the new Reflection of the class we can call Reflection’s getMethod() method. We’ll just pass in the private methods name and get back a ReflectionMethod object to modify (Line 16).

Here comes the real magic. The ReflectionMethod object has this neet method called setAccessible() which lets us change the visibility of an element. Here we are passing in true which will make it public (Line 19). There, we can now access the private method. Our last step is to call the method using ReflectionMethod’s invokeArgs(). This invokes the reflected method and passes its arguments as an array, similar to call_user_func_array(), returning the method’s results (Line 22).

Here is the final version of our invoke method. It is broken down into four steps.

/**
  * Call protected/private method of a class statically.
  *
  * @param $object
  * @param $methodName
  * @param array $parameters
  *
  * @return mixed
  */
public static function invoke($object, $methodName, array $parameters = array())
{
    // Get the name of the class our object is created from and create the reflected object.
    $reflection = new \ReflectionClass(get_class($object));

    // Grab the private method.
    $method = $reflection->getMethod($methodName);

    // Change the accessibility to public.
    $method->setAccessible(true);

    // Invoke the now public method with any passed parameters.
    return $method->invokeArgs($object, $parameters);
}

We now have access to the private method and any value returned from it.

The Implementation.

So I created this method and made it callable statically so that I could easily wrap it. Then I added a non-static wrapper method for those who like calling instantiated class methods. Both of these were then added to a trait.

Wait, what? a trait?

Yes, at first I was going to create a class for this method but realized that extending our test class would be problematic in some situations. We may need to extend something other than PHPUnit’s PHPUnit_Framework_TestCase class. Say if we use a TypeTestCase or a WebTestCase.

Also, we may even want to use it with some other testing framework like PhpSpec. Or, maybe not even in a test framework but for some other unforeseen implementation inside of a project. It needed to be architected in a way that could be utilized in the most ways possible. A trait is the first step in this design idea as I will go into later.

So here’s a basic example using PHPUnit as the testing framework.

use ClassWithPrivateMethod;

class ClassWithPrivateMethodTest extends \PHPUnit_Framework_TestCase {
    use \InvokePrivateMethodTrait;

    /** @var ClassWithPrivateMethod */
    private $classToTest;

    public function setUp(){
        $this->classToTest = new ClassWithPrivateMethod();
    }

    public function testSomeFunction(){
        $results = $this::invoke($this->classToTest, 'myPrivateMethod', ['foo', 'bar']);
        $this->assertEquals('ExpectedValue', $results)
    }
}

The Full Code.

Now for making it usable in every conceivable situation. We have a static method already, but some people prefer to call methods non-statically using the -> operator, who am I to judge. So I made an accessor method to call it. That’s all good, but what if you desire to use it in a stand-alone class and not a trait? Well, for that case I just created an empty class and called the trait. Just a wrapper for the trait basically, and we have our class implementation.

We now have four use cases covered. Static and non-static trait and static and non-static class. I suppose I could have also created a procedural method but we’re doing OO here and that would be beyond the scope.

Want to see the complete code? You can find it on GitHub

Composer distribution and complete documentation are also available through Packagist. If you would like to use it in your own project, It’s an MIT license so play with it to your heart’s content.

Disclosure.

Not all nails need a big hammer. Sometimes your shoe works.

After creating this package I talked with Sebastian Bergmann (@s_bergmann), the man behind PHPUnit, who directed me to a package he wrote that supplies a “Proxy for accessing non-public attributes and methods of an object.” It’s called peek-and-poke and can also be found on GitHub.

This is a beautiful implementation. But, it was a bit heavy for the situation I was coding for at that moment and didn’t meet the requirements, namely very small, lite, and PHP 5.4 compatible. That aside, it is a really cool library! Sebastian obviously put a lot of work into it making it solid and very robust. I will definitely be utilizing it in the future on larger projects where I need that kind of robustness and power. Thanks, Sebastian.