Est-il possible de configurer la maquette PHPUnit de cette façon?
$context = $this->getMockBuilder('Context')
->getMock();
$context->expects($this->any())
->method('offsetGet')
->with('Matcher')
->will($this->returnValue(new Matcher()));
$context->expects($this->any())
->method('offsetGet')
->with('Logger')
->will($this->returnValue(new Logger()));
J'utilise PHPUnit 3.5.10 et il échoue lorsque je demande Matcher car il attend l'argument "Logger". C'est comme si la deuxième attente réécrivait la première, mais quand je vide la maquette, tout semble correct.
Depuis PHPUnit 3.6, il existe $this->returnValueMap()
qui peut être utilisé pour renvoyer différentes valeurs en fonction des paramètres donnés au stub de la méthode.
Malheureusement, cela n'est pas possible avec l'API PHPUnit Mock par défaut.
Je peux voir deux options qui peuvent vous rapprocher de quelque chose comme ça:
$context = $this->getMockBuilder('Context')
->getMock();
$context->expects($this->at(0))
->method('offsetGet')
->with('Matcher')
->will($this->returnValue(new Matcher()));
$context->expects($this->at(1))
->method('offsetGet')
->with('Logger')
->will($this->returnValue(new Logger()));
Cela fonctionnera bien mais vous testez plus que vous ne devriez (principalement qu'il est appelé avec matcher en premier, et c'est un détail d'implémentation).
Cela échouera également si vous avez plus d'un appel à chacune des fonctions!
C'est plus de travail mais ça marche mieux puisque vous ne dépendez pas de l'ordre des appels:
<?php
class FooTest extends PHPUnit_Framework_TestCase {
public function testX() {
$context = $this->getMockBuilder('Context')
->getMock();
$context->expects($this->exactly(2))
->method('offsetGet')
->with($this->logicalOr(
$this->equalTo('Matcher'),
$this->equalTo('Logger')
))
->will($this->returnCallback(
function($param) {
var_dump(func_get_args());
// The first arg will be Matcher or Logger
// so something like "return new $param" should work here
}
));
$context->offsetGet("Matcher");
$context->offsetGet("Logger");
}
}
class Context {
public function offsetGet() { echo "org"; }
}
Cela produira:
/*
$ phpunit footest.php
PHPUnit 3.5.11 by Sebastian Bergmann.
array(1) {
[0]=>
string(7) "Matcher"
}
array(1) {
[0]=>
string(6) "Logger"
}
.
Time: 0 seconds, Memory: 3.00Mb
OK (1 test, 1 assertion)
J'ai utilisé $this->exactly(2)
dans le matcher pour montrer que cela fonctionne également avec le comptage des invocations. Si vous n'en avez pas besoin, le remplacer par $this->any()
fonctionnera, bien sûr.
Vous pouvez y parvenir avec un rappel:
class MockTest extends PHPUnit_Framework_TestCase
{
/**
* @dataProvider provideExpectedInstance
*/
public function testMockReturnsInstance($expectedInstance)
{
$context = $this->getMock('Context');
$context->expects($this->any())
->method('offsetGet')
// Accept any of "Matcher" or "Logger" for first argument
->with($this->logicalOr(
$this->equalTo('Matcher'),
$this->equalTo('Logger')
))
// Return what was passed to offsetGet as a new instance
->will($this->returnCallback(
function($arg1) {
return new $arg1;
}
));
$this->assertInstanceOf(
$expectedInstance,
$context->offsetGet($expectedInstance)
);
}
public function provideExpectedInstance()
{
return array_chunk(array('Matcher', 'Logger'), 1);
}
}
Doit passer pour tout argument "Logger" ou "Matcher" passé à la méthode offsetGet
du Context Mock:
F:\Work\code\gordon\sandbox>phpunit NewFileTest.php
PHPUnit 3.5.13 by Sebastian Bergmann.
..
Time: 0 seconds, Memory: 3.25Mb
OK (2 tests, 4 assertions)
Comme vous pouvez le voir, PHPUnit a exécuté deux tests. Un pour chaque valeur dataProvider. Et dans chacun de ces tests, il a fait l'assertion pour with()
et celle pour instanceOf
, d'où quatre assertions.
Dans le prolongement de la réponse de @edorian et des commentaires (@MarijnHuizendveld) concernant la garantie que la méthode est appelée avec Matcher et Logger, et pas simplement deux fois avec Matcher ou Logger, voici un exemple.
$expectedArguments = array('Matcher', 'Logger');
$context->expects($this->exactly(2))
->method('offsetGet')
->with($this->logicalOr(
$this->equalTo('Matcher'),
$this->equalTo('Logger')
))
->will($this->returnCallback(
function($param) use (&$expectedArguments){
if(($key = array_search($param, $expectedArguments)) !== false) {
// remove called argument from list
unset($expectedArguments[$key]);
}
// The first arg will be Matcher or Logger
// so something like "return new $param" should work here
}
));
// perform actions...
// check all arguments removed
$this->assertEquals(array(), $expectedArguments, 'Method offsetGet not called with all required arguments');
C'est avec PHPUnit 3.7.
Si la méthode que vous testez ne renvoie rien, et que vous devez simplement tester qu'elle est appelée avec les arguments corrects, la même approche s'applique. Pour ce scénario, j'ai également tenté de le faire en utilisant une fonction de rappel pour $ this-> callback comme argument du with, plutôt que de returnCallback dans le testament. Cela échoue, car phpunit appelle en interne le rappel deux fois dans le processus de vérification du rappel de la correspondance d'arguments. Cela signifie que l'approche échoue car au deuxième appel, cet argument a déjà été supprimé du tableau d'arguments attendu. Je ne sais pas pourquoi phpunit l'appelle deux fois (cela semble un gaspillage inutile), et je suppose que vous pouvez contourner cela en ne le supprimant que lors du deuxième appel, mais je n'étais pas assez confiant que c'est un comportement phpunit voulu et cohérent pour compter sur ce qui se passe.
Mes 2 cents au sujet: faites attention lorsque vous utilisez à ($ x): cela signifie que l'appel de méthode attendu sera le ($ x + 1) ème appel de méthode sur l'objet factice; cela ne signifie pas que ce sera le ($ x + 1) ème appel de la méthode attendue. Cela m'a fait perdre du temps, donc j'espère que ce ne sera pas avec vous. Cordialement à tous.
Je suis juste tombé sur cette PHP pour se moquer des objets: https://github.com/etsy/phpunit-extensions/wiki/Mock-Object
Voici également quelques solutions avec la bibliothèque doublit :
Solution 1: en utilisant Stubs::returnValueMap
/* Get a dummy double instance */
$double = Doublit::dummy_instance(Context::class);
/* Test the "offsetGet" method */
$double::_method('offsetGet')
// Test that the first argument is equal to "Matcher" or "Logger"
->args([Constraints::logicalOr('Matcher', 'Logger')])
// Return "new Matcher()" when first argument is "Matcher"
// Return "new Logger()" when first argument is "Logger"
->stub(Stubs::returnValueMap([['Matcher'], ['Logger']], [new Matcher(), new Logger()]));
Solution 2: utiliser un rappel
/* Get a dummy double instance */
$double = Doublit::dummy_instance(Context::class);
/* Test the "offsetGet" method */
$double::_method('offsetGet')
// Test that the first argument is equal to "Matcher" or "Logger"
->args([Constraints::logicalOr('Matcher', 'Logger')])
// Return "new Matcher()" when first argument $arg is "Matcher"
// Return "new Logger()" when first argument $arg is "Logger"
->stub(function($arg){
if($arg == 'Matcher'){
return new Matcher();
} else if($arg == 'Logger'){
return new Logger();
}
});