phpunit - mockbuilder - set mock object internal property

PhpPhpunit

Php Problem Overview


Is it possible to create a mock object with disabled constructor and manually setted protected properties?

Here is an idiotic example:

class A {
	protected $p;
	public function __construct(){
		$this->p = 1;
	}
	
	public function blah(){
		if ($this->p == 2)
			throw Exception();
	}
}

class ATest extend bla_TestCase {
	/** 
		@expectedException Exception
	*/
	public function testBlahShouldThrowExceptionBy2PValue(){
		$mockA = $this->getMockBuilder('A')
			->disableOriginalConstructor()
			->getMock();
		$mockA->p=2; //this won't work because p is protected, how to inject the p value?
		$mockA->blah();
	}
}

So I wanna inject the p value which is protected, so I can't. Should I define setter or IoC, or I can do this with phpunit?

Php Solutions


Solution 1 - Php

You can make the property public by using Reflection, and then set the desired value:

$a = new A;
$reflection = new ReflectionClass($a);
$reflection_property = $reflection->getProperty('p');
$reflection_property->setAccessible(true);

$reflection_property->setValue($a, 2);

Anyway in your example you don't need to set p value for the Exception to be raised. You are using a mock for being able to take control over the object behaviour, without taking into account it's internals.

So, instead of setting p = 2 so an Exception is raised, you configure the mock to raise an Exception when the blah method is called:

$mockA = $this->getMockBuilder('A')
        ->disableOriginalConstructor()
        ->getMock();
$mockA->expects($this->any())
         ->method('blah')
         ->will($this->throwException(new Exception));

Last, it's strange that you're mocking the A class in the ATest. You usually mock the dependencies needed by the object you're testing.

Hope this helps.

Solution 2 - Php

Thought i'd leave a handy helper method that could be quickly copy and pasted here:

/**
 * Sets a protected property on a given object via reflection
 *
 * @param $object - instance in which protected value is being modified
 * @param $property - property on instance being modified
 * @param $value - new value of the property being modified
 *
 * @return void
 */
public function setProtectedProperty($object, $property, $value)
{
    $reflection = new ReflectionClass($object);
    $reflection_property = $reflection->getProperty($property);
    $reflection_property->setAccessible(true);
    $reflection_property->setValue($object, $value);
}

Solution 3 - Php

Based on the accepted answer from @gontrollez, since we are using a mock builder we do not have the need to call new A; since we can use the class name instead.

    $a = $this->getMockBuilder(A::class)
        ->disableOriginalConstructor()
        ->getMock();

    $reflection = new ReflectionClass(A::class);
    $reflection_property = $reflection->getProperty('p');
    $reflection_property->setAccessible(true);

    $reflection_property->setValue($a, 2);

Solution 4 - Php

Based on @rsahai91 answer above, created a new helper for making multiple methods accessible. Can be private or protected

/**
 * Makes any properties (private/protected etc) accessible on a given object via reflection
 *
 * @param $object - instance in which properties are being modified
 * @param array $properties - associative array ['propertyName' => 'propertyValue']
 * @return void
 * @throws ReflectionException
 */
public function setProperties($object, $properties)
{
    $reflection = new ReflectionClass($object);
    foreach ($properties as $name => $value) {
        $reflection_property = $reflection->getProperty($name);
        $reflection_property->setAccessible(true);
        $reflection_property->setValue($object, $value);
    }
}

Example use:

$mock = $this->createMock(MyClass::class);

$this->setProperties($mock, [
    'propname1' => 'valueOfPrivateProp1',
    'propname2' => 'valueOfPrivateProp2'
]);

Solution 5 - Php

It would be amazing if every codebase used DI and IoC, and never did stuff like this:

public function __construct(BlahClass $blah)
{
    $this->protectedProperty = new FooClass($blah);
}

You can use a mock BlahClass in the constructor, sure, but then the constructor sets a protected property to something you CAN'T mock.

So you're probably thinking "Well refactor the constructor to take a FooClass instead of a BlahClass, then you don't have to instantiate the FooClass in the constructor, and you can put in a mock instead!" Well, you'd be right, if that didn't mean you would have to change every usage of the class in the entire codebase to give it a FooClass instead of a BlahClass.

Not every codebase is perfect, and sometimes you just need to get stuff done. And that means, yes, sometimes you need to break the "only test public APIs" rule.

Solution 6 - Php

You may want to use the ReflectionProperty as well.

$reflection = new ReflectionProperty(Foo::class, 'myProperty');
$reflection->setAccessible(true); // Do this if less than PHP8.1.
$reflection->setValue($yourMock, 'theValue');

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
Questioninf3rnoView Question on Stackoverflow
Solution 1 - PhpgontrollezView Answer on Stackoverflow
Solution 2 - Phprsahai91View Answer on Stackoverflow
Solution 3 - PhpFreefriView Answer on Stackoverflow
Solution 4 - PhpOli GirlingView Answer on Stackoverflow
Solution 5 - PhpZachary BurnhamView Answer on Stackoverflow
Solution 6 - PhpRomain SickenbergView Answer on Stackoverflow