web-dev-qa-db-fra.com

phpunit évite les arguments du constructeur pour la simulation

Comment éviter que phpunit doive appeler le constructeur pour un objet simulé? Sinon, j'aurais besoin d'un objet simulé comme argument constructeur, un autre pour cela, etc. L'API semble être comme ceci:

getMock($className, $methods = array(), array $arguments = array(),
        $mockClassName = '', $callOriginalConstructor = TRUE,
        $callOriginalClone = TRUE, $callAutoload = TRUE)

Je ne le fais pas fonctionner. Il se plaint toujours de l'argument constructeur, même avec $callOriginalConstructor défini sur false.

Je n'ai qu'un seul objet dans le constructeur et c'est une injection de dépendance. Je ne pense donc pas que j'ai un problème de conception là-bas.

80
Koen

Vous pouvez utiliser getMockBuilder au lieu de simplement getMock:

$mock = $this->getMockBuilder('class_name')
    ->disableOriginalConstructor()
    ->getMock();

Voir la section sur "Test Doubles" dans documentation de PHPUnit pour plus de détails.

Bien que vous puissiez le faire, il est préférable de ne pas en avoir besoin. Vous pouvez refactoriser votre code, donc au lieu d'une classe concrète (avec un constructeur) devant être injectée, vous ne dépendez que d'une interface. Cela signifie que vous pouvez simuler ou bloquer l'interface sans avoir à dire à PHPUnit de modifier le comportement du constructeur.

136
dave1010

Voici:

    // Get a Mock Soap Client object to work with.
    $classToMock = 'SoapClient';
    $methodsToMock = array('__getFunctions');
    $mockConstructorParams = array('fake wsdl url', array());
    $mockClassName = 'MyMockSoapClient';
    $callMockConstructor = false;
    $mockSoapClient = $this->getMock($classToMock,
                                     $methodsToMock,
                                     $mockConstructorParams,
                                     $mockClassName,
                                     $callMockConstructor);
42
Matthew Purdon

En complément, je voulais attacher des appels expects() à mon objet simulé, puis appeler le constructeur. Dans PHPUnit 3.7.14, l'objet renvoyé lorsque vous appelez disableOriginalConstructor() est littéralement un objet.

// Use a trick to create a new object of a class
// without invoking its constructor.
$object = unserialize(
sprintf('O:%d:"%s":0:{}', strlen($className), $className)

Malheureusement, dans PHP 5.4 il y a une nouvelle option qu'ils n'utilisent pas:

ReflectionClass :: newInstanceWithoutConstructor

Comme cela n'était pas disponible, j'ai dû refléter manuellement la classe, puis invoquer le constructeur.

$mock = $this->getMockBuilder('class_name')
    ->disableOriginalConstructor()
    ->getMock();

$mock->expect($this->once())
    ->method('functionCallFromConstructor')
    ->with($this->equalTo('someValue'));

$reflectedClass = new ReflectionClass('class_name');
$constructor = $reflectedClass->getConstructor();
$constructor->invoke($mock);

Notez que si functionCallFromConstruct est protected, vous devez spécifiquement utiliser setMethods() pour que la méthode protégée soit mockée. Exemple:

    $mock->setMethods(array('functionCallFromConstructor'));

setMethods() doit être appelé avant l'appel de expect(). Personnellement, je l'enchaîne après disableOriginalConstructor() mais avant getMock().

3
Steve Tauber

Vous devez peut-être créer un stub à passer comme argument constructeur. Ensuite, vous pouvez briser cette chaîne d'objets fictifs.

1
Glenn Moss

Alternativement, vous pouvez ajouter un paramètre à getMock pour empêcher l'appel du constructeur par défaut.

$mock = $this->getMock(class_name, methods = array(), args = array(), 
        mockClassName = '', callOriginalConstructor = FALSE);

Pourtant, je pense que la réponse de dave1010 est plus belle, c'est juste pour être complet.

1
Hans Wouters

PHPUnit est conçu pour appeler le constructeur sur des objets simulés; pour éviter cela, vous devez soit:

  1. Injecter un objet fantaisie en tant que dépendance dans l'objet dont vous avez du mal à vous moquer
  2. Créez une classe de test qui étend la classe que vous essayez d'appeler sans appeler le constructeur parent
0
silfreed