phpunit - mockbuilder - définir l'objet fantaisie intérieur de la propriété
Est-il possible de créer un objet fictif avec un constructeur désactivé et des propriétés protégées définies manuellement?
Voici un exemple idiot:
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();
}
}
Donc je veux injecter la valeur p qui est protégée, donc je ne peux pas. devrais-je définir setter ou IoC, ou je peux le faire avec phpunit?
3 réponses
Vous pouvez rendre la propriété publique à L'aide de Reflection, puis définir la valeur souhaitée:
$a = new A;
$reflection = new ReflectionClass($a);
$reflection_property = $reflection->getProperty('p');
$reflection_property->setAccessible(true);
$reflection_property->setValue($a, 2);
Quoi qu'il en soit, dans votre exemple, vous n'avez pas besoin de définir la valeur p pour que l'Exception soit levée. Vous utilisez un simulacre pour pouvoir prendre le contrôle du comportement de l'objet, sans prendre en compte ses internes.
Donc, au lieu de définir p = 2 pour qu'une Exception soit levée, vous configurez le mock pour déclencher une Exception lorsque la méthode blah est appelée:
$mockA = $this->getMockBuilder('A')
->disableOriginalConstructor()
->getMock();
$mockA->expects($this->any())
->method('blah')
->will($this->throwException(new Exception));
Enfin, c'est étrange que vous vous moquiez de la classe A dans L'ATest. Vous vous moquez généralement des dépendances nécessaires à l'objet que vous testez.
J'espère que cela aide.
Je pensais laisser une méthode d'aide pratique qui pourrait être rapidement copiée et collée ici:
/**
* 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);
}
Ce serait incroyable si chaque base de code utilisait DI et IoC, et ne faisait jamais des choses comme ça:
public function __construct(BlahClass $blah)
{
$this->protectedProperty = new FooClass($blah);
}
Vous pouvez utiliser une blahclass simulée dans le constructeur, bien sûr, mais le constructeur définit une propriété protégée sur quelque chose que vous ne pouvez pas simuler.
Donc vous pensez probablement "bien refactoriser le constructeur pour prendre un FooClass au lieu D'un BlahClass, alors vous n'avez pas besoin d'instancier le FooClass dans le constructeur, et vous pouvez mettre un mock à la place!"Eh bien, vous avez raison, si c' cela ne signifiait pas que vous deviez changer chaque utilisation de la classe dans toute la base de code pour lui donner un FooClass au lieu d'un BlahClass.
Toutes les bases de code ne sont pas parfaites, et parfois vous avez juste besoin de faire des choses. Et cela signifie, Oui, parfois vous devez casser la règle" only test public API".