Matthew Daly's Blog

I'm a web developer in Norfolk. This is my blog...

7th September 2019 8:16 pm

Setting Private Properties in Tests

Sometimes when writing a test, you come across a situation where you need to set a private field that’s not accessible through any existing route. For instance, I’ve been working with Doctrine a bit lately, and since the ID on an entity is generated automatically, it should not be possible to change it via a setter, but at the same time, we sometimes have the need to set it in a test.

Fortunately, there is a way to do that. Using PHP’s reflection API, you can temporarily mark a property on an object as accessible, so as to be able to set it without either passing it to the constructor or creating a setter method that will only ever be used by the test. We first create a ReflectionClass instance from the object, then get the property. We mark it as accessible, and then set the value on the instance, as shown below:

<?php declare(strict_types = 1);
namespace Tests\Unit;
use Tests\TestCase;
use Project\Document;
use ReflectionClass;
final class DocumentTest extends TestCase
{
public function testGetId()
{
$doc = new Document();
$reflect = new ReflectionClass($doc);
$id = $reflect->getProperty('id');
$id->setAccessible(true);
$id->setValue($doc, 1);
$this->assertEquals(1, $doc->getId());
}
}

If you’re likely to need this in more than one place, you may want to pull this functionality out into a trait for reuse:

<?php declare(strict_types = 1);
namespace Tests\Traits;
use ReflectionClass;
trait SetsPrivateProperties
{
/**
* Sets a private property
*
* @param mixed $object
* @param string $property
* @param mixed $value
* @return void
*/
public function setPrivateProperty($object, string $property, $value)
{
$reflect = new ReflectionClass($object);
$prop = $reflect->getProperty($property);
$prop->setAccessible(true);
$prop->setValue($object, $value);
$prop->setAccessible(false);
}
}

Then your test can be simplified as follows:

<?php declare(strict_types = 1);
namespace Tests\Unit;
use Tests\TestCase;
use Project\Document;
use Tests\Traits\SetsPrivateProperties;
final class DocumentTest extends TestCase
{
use SetsPrivateProperties;
public function testGetId()
{
$doc = new Document();
$this->setPrivateProperty($doc, 'id', 1);
$this->assertEquals(1, $doc->getId());
}
}

While this is a slightly contrived and limited example, and this situation is quite rare, I’ve found it to be a useful technique under certain circumstances.

Recent Posts

Flexible Data Types With the JSON Field

Storing Wordpress Configuration in Environment Variables

Using Mix Versioning Outside Laravel

Setting Private Properties in Tests

Skipping Environment Specific Phpunit Tests

About me

I'm a web and mobile app developer based in Norfolk. My skillset includes Python, PHP and Javascript, and I have extensive experience working with CodeIgniter, Laravel, Zend Framework, Django, Phonegap and React.js.