Dealing with protected/private methods or properties

By @aaronsaray · 2021-10-21 20:11 (edited)

Dealing with protected/private methods or properties

In a perfect world, we should not be digging into protected or private methods or properties in our unit tests. That's a bad test smell! However, there are some times when I've found this useful. And for that, I use two different methods that I put in my bootstrap.php file that PHPUnit loads.

First, let's say you want to get a property from a class in a test:

class Mine
  protected $about = '';

  public function __construct()
    $this->about = 'me';

And we want to test the about property. Again, we probably don't need or want to do this - but just in case, you could do something like this:

public function testItsAllAboutMe(): void
  $mine = new Mine();
  $this->assertEquals('me', getProperty($mine, 'about'));

Now, what is getProperty():

function getProperty($object, $propertyName)
  $reflection = new \ReflectionClass($object);
  $property = $reflection->getProperty($propertyName);
  return $property->getValue($object);

This works by creating a reflection class of the method, altering the property visibility and then returning the value.

What about if you want to call a method?

function callMethod($object, $methodName, array $arguments = [])
  $class = new \ReflectionClass($object);
  $method = $class->getMethod($methodName);
  return empty($arguments) ? $method->invoke($object) : $method->invokeArgs($object, $arguments);

There you go.

I feel like I have to say it again - you probably don't want to use this, but in the very few rare times that you can't mock out something - or setting up a test scenario with only public access is too verbose, tiring or expensive, you can access protected or private elements this way.

Oh and I have an archived package called package-for-laravel/testing-framework - that you can check out for how I implemented this in my own projects - as well as some other useful nuggets.

  • By @samuel · 2021-10-21 21:35

    Good tip. Generally having to access non-public properties is a sign of poor code, but I see two scenarios when this is useful — when something really shouldn't be public but you just need to test it (without adding accessors either, since the value should really be "hidden"), and when working with third party libraries.

    Third party libraries, especially old ones, often poorly account for people's use cases, so you sometimes have to access or set protected properties even in your normal code. I've had this experience with a few SDKs for some services' APIs.

    Good article!

    • By @aaronsaray · 2021-10-21 21:52

      You're reminding me of some very locked down libraries I've used in the past - with many final classes/methods and private properties - without any dependency injection! :)