web-dev-qa-db-fra.com

phpunit - mockbuilder - définit la propriété interne d'un objet mock

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?

33
inf3rno

Vous pouvez rendre la propriété publique à l'aide de Reflection, puis définissez 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 se déclenche. Vous utilisez un simulacre pour pouvoir contrôler le comportement de l'objet sans tenir compte de ses éléments internes.

Ainsi, au lieu de définir p = 2 pour qu'une exception soit déclenchée, vous configurez le simulacre pour qu'il déclenche 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, il est étrange que vous vous moquiez de la classe A dans l'EST. Vous vous moquez généralement des dépendances requises par l'objet que vous testez.

J'espère que cela t'aides.

44
gontrollez

J'ai pensé laisser une méthode d'assistance 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);
}
17
rsahai91

Ce serait étonnant si chaque base de code utilisait DI et IoC, et ne faisait jamais ce genre de choses: 

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

Vous pouvez utiliser une maquette BlahClass dans le constructeur, bien sûr, mais le constructeur définit ensuite une propriété protégée sur quelque chose que vous ne pouvez pas simuler.

Donc vous pensez probablement "Bien refactoriser le constructeur pour qu'il prenne un FooClass au lieu d'un BlahClass, vous n'avez pas besoin d'instancier le FooClass dans le constructeur, et vous pouvez créer une maquette à la place!" Eh bien, vous auriez raison, si cela ne signifiait pas que vous deviez changer chaque utilisation de la classe dans la base de code entière 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 quelque chose. Et cela signifie que, oui, vous devez parfois enfreindre la règle "tester uniquement les API publiques".

0
Zachary Burnham