Is it possible to create a mock object with disabled constructor and manually setted protected properties?
Here is an idiotic example:
class A {
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'
]);
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.
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.
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);
}
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);