Wie kann ich PHPUnit MockObjects dazu bringen, basierend auf einem Parameter unterschiedliche Werte zurückzugeben?

141

Ich habe ein PHPUnit-Scheinobjekt, das 'return value'unabhängig von seinen Argumenten zurückgibt :

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

Ich möchte in der Lage sein, einen anderen Wert zurückzugeben, der auf den an die Mock-Methode übergebenen Argumenten basiert. Ich habe so etwas versucht wie:

$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"'));

Dies führt jedoch dazu, dass sich PHPUnit beschwert, wenn der Mock nicht mit dem Argument aufgerufen wird. Daher gehe 'two'ich davon aus, dass die Definition von methodToMock('two')die Definition der ersten überschreibt.

Meine Frage lautet also: Gibt es eine Möglichkeit, ein PHPUnit-Scheinobjekt dazu zu bringen, basierend auf seinen Argumenten einen anderen Wert zurückzugeben? Und wenn ja, wie?

Ben Dowling
quelle

Antworten:

125

Verwenden Sie einen Rückruf. zB (direkt aus der PHPUnit-Dokumentation):

<?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();
    // ...
}
?>

Führen Sie die gewünschte Verarbeitung in callback () aus und geben Sie das Ergebnis basierend auf Ihren $ args zurück.

Howard Sandford
quelle
2
Können Sie einen Link zur Dokumentation bereitstellen? Ich kann es nicht mit "the Google" finden
Kris Erickson
6
Beachten Sie, dass Sie eine Methode als Rückruf verwenden können, indem Sie ein Array übergeben, z $this->returnCallback(array('MyClassTest','myCallback')) .
Patrick Fisher
1
Es sollte auch möglich sein, einen Verschluss direkt an ihn weiterzugeben
Ocramius
7
Dies sollte nur in seltenen Fällen verwendet werden. Ich würde vorschlagen, stattdessen returnValueMap zu verwenden, da keine benutzerdefinierte Logik in den Rückruf geschrieben werden muss.
Herman J. Radtke III
1
Ich kann dir nicht genug danken. Bei PHP-Versionen> 5.4 können Sie auch eine anonyme Funktion als Rückruf verwenden. $this->returnCallback(function() { // ... })
Bmorenate
110

Aus den neuesten phpUnit-Dokumenten: "Manchmal sollte eine Stubbed-Methode abhängig von einer vordefinierten Liste von Argumenten unterschiedliche Werte zurückgeben. Mit returnValueMap () können Sie eine Zuordnung erstellen, die Argumente mit entsprechenden Rückgabewerten verknüpft."

$mock->expects($this->any())
    ->method('getConfigValue')
    ->will(
        $this->returnValueMap(
            array(
                array('firstparam', 'secondparam', 'retval'),
                array('modes', 'foo', array('Array', 'of', 'modes'))
            )
        )
    );
Nikola Ivancevic
quelle
3
Der Link im Beitrag ist alt, der richtige ist hier: returnValueMap ()
hejdav
48

Ich hatte ein ähnliches Problem (obwohl etwas anders ... Ich brauchte keinen anderen Rückgabewert basierend auf Argumenten, sondern musste testen, um sicherzustellen, dass 2 Sätze von Argumenten an dieselbe Funktion übergeben wurden). Ich bin auf so etwas gestoßen:

$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(...));

Es ist nicht perfekt, da es erfordert , dass die Reihenfolge der zwei Anrufe zu foo () bekannt ist , aber in der Praxis ist dies wahrscheinlich nicht allzu schlecht.

Adam
quelle
28

Sie möchten wahrscheinlich einen Rückruf auf OOP-Weise durchführen:

<?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
quelle
16

Es ist nicht genau das, was Sie fragen, aber in einigen Fällen kann es helfen:

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

onConsecutiveCalls - Gibt eine Liste von Werten in der angegebenen Reihenfolge zurück

Prokhor Sednev
quelle
4

Übergeben Sie ein Array mit zwei Ebenen, wobei jedes Element ein Array von:

  • Zuerst sind Methodenparameter und am wenigsten der Rückgabewert.

Beispiel:

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

Sie können das Argument auch wie folgt zurückgeben:

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

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

Wie Sie in der Mocking-Dokumentation sehen können , können Sie mit der Methode returnValue($index)das angegebene Argument zurückgeben.

Gabriel Gcia Fdez
quelle
0

Meinst du so etwas?

public function TestSomeCondition($condition){
  $mockObj = $this->getMockObject();
  $mockObj->setReturnValue('yourMethod',$condition);
}
eddy147
quelle
Ich denke, das ist SimpleTest-Code, nicht PHPUnit. Aber nein, das will ich nicht erreichen. Angenommen, ich hatte ein Scheinobjekt, das ein Wort für eine bestimmte Nummer zurückgab. Meine Scheinmethode müsste "eins" zurückgeben, wenn sie mit 1 aufgerufen wird, "zwei", wenn sie mit 2 usw. aufgerufen wird. $
Ben Dowling
0

Ich hatte ein ähnliches Problem, das ich auch nicht lösen konnte (es gibt überraschend wenig Informationen für PHPUnit). In meinem Fall habe ich gerade jeden Test separat durchgeführt - bekannte Eingabe und bekannte Ausgabe. Mir wurde klar, dass ich kein Allround-Scheinobjekt erstellen musste, sondern nur ein bestimmtes für einen bestimmten Test. Daher habe ich die Tests getrennt und kann einzelne Aspekte meines Codes als separate testen Einheit. Ich bin mir nicht sicher, ob dies auf Sie zutrifft oder nicht, aber das hängt davon ab, was Sie testen müssen.

JamShady
quelle
Leider würde das in meiner Situation nicht funktionieren. Der Mock wird an eine Methode übergeben, die ich teste, und die Testmethode ruft die verspottete Methode mit verschiedenen Argumenten auf. Es ist interessant zu wissen, dass Sie das Problem nicht lösen konnten. Es hört sich so an, als könnte dies eine PHPUnit-Einschränkung sein.
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
quelle
-2

Versuchen :

->with($this->equalTo('one'),$this->equalTo('two))->will($this->returnValue('return value'));
erenon
quelle
Diese Antwort gilt nicht auf die ursprüngliche Frage, aber es Details ein ähnliches Problem hatte ich: überprüfen, ob ein bestimmte Satz von Parametern zur Verfügung gestellt wird. PHPUnits with () akzeptiert mehrere Argumente, einen Matcher für jeden Parameter.
TaZ