Comment puis-je faire en sorte que PHPUnit MockObjects renvoie différentes valeurs en fonction d'un paramètre?

141

J'ai un objet simulé PHPUnit qui renvoie 'return value'quels que soient ses arguments:

// From inside a test...
$mock = $this->getMock('myObject', 'methodToMock');
$mock->expects($this->any))
     ->method('methodToMock')
     ->will($this->returnValue('return value'));

Ce que je veux pouvoir faire, c'est renvoyer une valeur différente en fonction des arguments passés à la méthode fictive. J'ai essayé quelque chose comme:

$mock = $this->getMock('myObject', 'methodToMock');

// methodToMock('one')
$mock->expects($this->any))
     ->method('methodToMock')
     ->with($this->equalTo('one'))
     ->will($this->returnValue('method called with argument "one"'));

// methodToMock('two')
$mock->expects($this->any))
     ->method('methodToMock')
     ->with($this->equalTo('two'))
     ->will($this->returnValue('method called with argument "two"'));

Mais cela amène PHPUnit à se plaindre si le mock n'est pas appelé avec l'argument 'two', donc je suppose que la définition de methodToMock('two')écrase la définition de la première.

Ma question est donc la suivante: existe-t-il un moyen d'obtenir un objet simulé PHPUnit pour renvoyer une valeur différente en fonction de ses arguments? Et si oui, comment?

Ben Dowling
la source

Réponses:

125

Utilisez un rappel. par exemple (directement depuis la documentation PHPUnit):

<?php
class StubTest extends PHPUnit_Framework_TestCase
{
    public function testReturnCallbackStub()
    {
        $stub = $this->getMock(
          'SomeClass', array('doSomething')
        );

        $stub->expects($this->any())
             ->method('doSomething')
             ->will($this->returnCallback('callback'));

        // $stub->doSomething() returns callback(...)
    }
}

function callback() {
    $args = func_get_args();
    // ...
}
?>

Faites le traitement que vous voulez dans le callback () et renvoyez le résultat en fonction de vos $ args, le cas échéant.

Howard Sandford
la source
2
Pouvez-vous fournir un lien vers la documentation? Je n'arrive pas à le trouver avec "le Google"
Kris Erickson
6
Notez que vous pouvez utiliser une méthode comme rappel en passant un tableau, par exemple $this->returnCallback(array('MyClassTest','myCallback')).
Patrick Fisher
1
Il devrait également être possible de lui passer directement une fermeture
Ocramius
7
Cela ne doit être utilisé que dans de rares cas. Je suggérerais d'utiliser returnValueMap à la place car il ne nécessite pas d'écriture de logique personnalisée dans le rappel.
Herman J. Radtke III
1
Je ne peux pas vous remercier assez. De plus, avec les versions PHP> 5.4, vous pouvez utiliser une fonction anonyme comme rappel. $this->returnCallback(function() { // ... })
bmorenate
110

D'après la dernière documentation de phpUnit: "Parfois, une méthode stubbed doit renvoyer des valeurs différentes selon une liste d'arguments prédéfinie. Vous pouvez utiliser returnValueMap () pour créer une carte qui associe des arguments aux valeurs de retour correspondantes."

$mock->expects($this->any())
    ->method('getConfigValue')
    ->will(
        $this->returnValueMap(
            array(
                array('firstparam', 'secondparam', 'retval'),
                array('modes', 'foo', array('Array', 'of', 'modes'))
            )
        )
    );
Nikola Ivancevic
la source
3
Le lien dans le message est ancien, le bon est ici: returnValueMap ()
hejdav
49

J'ai eu un problème similaire (bien que légèrement différent ... je n'avais pas besoin de valeur de retour différente en fonction des arguments, mais j'ai dû tester pour m'assurer que 2 ensembles d'arguments étaient passés à la même fonction). Je suis tombé sur l'utilisation de quelque chose comme ceci:

$mock = $this->getMock();
$mock->expects($this->at(0))
    ->method('foo')
    ->with(...)
    ->will($this->returnValue(...));

$mock->expects($this->at(1))
    ->method('foo')
    ->with(...)
    ->will($this->returnValue(...));

Ce n'est pas parfait, car cela nécessite que l'ordre des 2 appels à foo () soit connu, mais en pratique ce n'est probablement pas trop mal.

Adam
la source
28

Vous voudrez probablement faire un rappel en mode POO:

<?php
class StubTest extends PHPUnit_Framework_TestCase
{
    public function testReturnAction()
    {
        $object = $this->getMock('class_name', array('method_to_mock'));
        $object->expects($this->any())
            ->method('method_to_mock')
            ->will($this->returnCallback(array($this, 'returnCallback'));

        $object->returnAction('param1');
        // assert what param1 should return here

        $object->returnAction('param2');
        // assert what param2 should return here
    }

    public function returnCallback()
    {
        $args = func_get_args();

        // process $args[0] here and return the data you want to mock
        return 'The parameter was ' . $args[0];
    }
}
?>
Francis Lewis
la source
16

Ce n'est pas exactement ce que vous demandez, mais dans certains cas, cela peut aider:

$mock->expects( $this->any() ) )
 ->method( 'methodToMock' )
 ->will( $this->onConsecutiveCalls( 'one', 'two' ) );

onConsecutiveCalls - renvoie une liste de valeurs dans l'ordre spécifié

Prokhor Sednev
la source
4

Passez un tableau à deux niveaux, où chaque élément est un tableau de:

  • les premiers sont les paramètres de méthode, et le moins est la valeur de retour.

exemple:

->willReturnMap([
    ['firstArg', 'secondArg', 'returnValue']
])
Antonmarin
la source
2

Vous pouvez également renvoyer l'argument comme suit:

$stub = $this->getMock(
  'SomeClass', array('doSomething')
);

$stub->expects($this->any())
     ->method('doSomething')
     ->will($this->returnArgument(0));

Comme vous pouvez le voir dans la documentation Mocking , la méthode returnValue($index)permet de retourner l'argument donné.

Gabriel Gcia Fdez
la source
0

Voulez-vous dire quelque chose comme ça?

public function TestSomeCondition($condition){
  $mockObj = $this->getMockObject();
  $mockObj->setReturnValue('yourMethod',$condition);
}
tourbillon147
la source
Je pense que c'est du code SimpleTest, pas PHPUnit. Mais non, ce n'est pas ce que je veux réaliser. Disons que j'avais un objet simulé qui renvoyait un mot pour un nombre donné. Ma méthode fictive devrait renvoyer «un» lorsqu'elle est appelée avec 1, «deux» lorsqu'elle est appelée avec 2, etc. $
Ben Dowling
0

J'ai eu un problème similaire que je ne pouvais pas résoudre non plus (il y a étonnamment peu d'informations sur PHPUnit). Dans mon cas, je viens de faire chaque test test séparé - entrée connue et sortie connue. J'ai réalisé que je n'avais pas besoin de créer un objet simulé touche-à-tout, je n'avais besoin que d'un objet spécifique pour un test spécifique, et j'ai donc séparé les tests et je peux tester des aspects individuels de mon code séparément. unité. Je ne sais pas si cela peut s'appliquer à vous ou non, mais c'est ce que vous devez tester.

JamShady
la source
Malheureusement, cela ne fonctionnerait pas dans ma situation. Le simulacre est passé dans une méthode que je teste et la méthode de test appelle la méthode simulée avec différents arguments. Il est intéressant de savoir que vous ne pouvez pas résoudre le problème. Il semble que cela pourrait être une limitation de PHPUnit.
Ben Dowling
-1
$this->BusinessMock = $this->createMock('AppBundle\Entity\Business');

    public function testBusiness()
    {
        /*
            onConcecutiveCalls : Whether you want that the Stub returns differents values when it will be called .
        */
        $this->BusinessMock ->method('getEmployees')
                                ->will($this->onConsecutiveCalls(
                                            $this->returnArgument(0),
                                            $this->returnValue('employee')                                      
                                            )
                                      );
        // first call

        $this->assertInstanceOf( //$this->returnArgument(0),
                'argument',
                $this->BusinessMock->getEmployees()
                );
       // second call


        $this->assertEquals('employee',$this->BusinessMock->getEmployees()) 
      //$this->returnValue('employee'),


    }
Jjoselon
la source
-2

Essayez:

->with($this->equalTo('one'),$this->equalTo('two))->will($this->returnValue('return value'));
Erenon
la source
Cette réponse ne s'applique pas à la question d'origine, mais elle détaille un problème similaire que j'ai eu: vérifier qu'un certain ensemble de paramètres est fourni. PHPUnit's with () accepte plusieurs arguments, un matcher pour chaque paramètre.
TaZ