Jak mogę sprawić, aby PHPUnit MockObjects zwracały różne wartości na podstawie parametru?

141

Mam obiekt makiety PHPUnit, który zwraca 'return value'niezależnie od argumentów:

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

Chcę mieć możliwość zwrócenia innej wartości na podstawie argumentów przekazanych do metody mock. Próbowałem czegoś takiego:

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

Ale to powoduje, że PHPUnit narzeka, jeśli makieta nie zostanie wywołana z argumentem 'two', więc zakładam, że definicja methodToMock('two')nadpisuje definicję pierwszej.

Moje pytanie brzmi: czy istnieje sposób, aby obiekt makiety PHPUnit zwracał inną wartość na podstawie jego argumentów? A jeśli tak, to w jaki sposób?

Ben Dowling
źródło

Odpowiedzi:

125

Użyj oddzwonienia. np. (prosto z dokumentacji 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();
    // ...
}
?>

Wykonaj dowolne przetwarzanie w funkcji callback () i zwróć wynik w oparciu o swoje argumenty $ odpowiednio.

Howard Sandford
źródło
2
Czy możesz podać link do dokumentacji? Nie mogę go znaleźć w „Google”
Kris Erickson
6
Zauważ, że możesz użyć metody jako wywołania zwrotnego, przekazując tablicę, np $this->returnCallback(array('MyClassTest','myCallback')).
Patrick Fisher,
1
Powinno być również możliwe bezpośrednie przejście do niego
Ocramius
7
Powinno to być używane tylko w rzadkich przypadkach. Sugerowałbym użycie zamiast tego returnValueMap, ponieważ nie wymaga to zapisywania niestandardowej logiki w wywołaniu zwrotnym.
Herman J. Radtke III,
1
Nie mogę ci wystarczająco podziękować. Ponadto w przypadku wersji PHP> 5.4 możesz użyć funkcji anonimowej jako wywołania zwrotnego. $this->returnCallback(function() { // ... })
bmorenate
110

Z najnowszej dokumentacji phpUnit: "Czasami metoda typu stub powinna zwracać różne wartości w zależności od predefiniowanej listy argumentów. Możesz użyć funkcji returnValueMap (), aby utworzyć mapę, która kojarzy argumenty z odpowiadającymi im wartościami zwracanymi."

$mock->expects($this->any())
    ->method('getConfigValue')
    ->will(
        $this->returnValueMap(
            array(
                array('firstparam', 'secondparam', 'retval'),
                array('modes', 'foo', array('Array', 'of', 'modes'))
            )
        )
    );
Nikola Ivancevic
źródło
3
Link w poście jest stary, poprawny tutaj: returnValueMap ()
hejdav
49

Miałem podobny problem (chociaż trochę inny ... Nie potrzebowałem innej wartości zwracanej na podstawie argumentów, ale musiałem przetestować, aby upewnić się, że 2 zestawy argumentów są przekazywane do tej samej funkcji). Natknąłem się na użycie czegoś takiego:

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

Nie jest doskonały, ponieważ wymaga, aby znana była kolejność dwóch wywołań funkcji foo (), ale w praktyce prawdopodobnie nie jest tak źle.

Adam
źródło
28

Prawdopodobnie chciałbyś wykonać oddzwonienie w stylu OOP:

<?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
źródło
16

Nie jest to dokładnie to, o co pytasz, ale w niektórych przypadkach może pomóc:

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

onConsecutiveCalls - zwraca listę wartości w określonej kolejności

Prokhor Sednev
źródło
4

Przekaż dwupoziomową tablicę, gdzie każdy element jest tablicą:

  • najpierw są parametry metody, a najmniej wartość zwracana.

przykład:

->willReturnMap([
    ['firstArg', 'secondArg', 'returnValue']
])
antonmarin
źródło
2

Możesz również zwrócić argument w następujący sposób:

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

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

Jak widać w dokumentacji Mocking , metoda returnValue($index)pozwala zwrócić podany argument.

Gabriel Gcia Fdez
źródło
0

Masz na myśli coś takiego?

public function TestSomeCondition($condition){
  $mockObj = $this->getMockObject();
  $mockObj->setReturnValue('yourMethod',$condition);
}
eddy147
źródło
Myślę, że to kod SimpleTest, a nie PHPUnit. Ale nie, to nie jest to, co chcę osiągnąć. Powiedzmy, że miałem pozorowany obiekt, który zwrócił słowo dla podanej liczby. Moje mock metoda musiałaby wrócić „jeden” przy nazwie z 1, „dwa” przy nazwie z 2 itd $
Ben Dowling
0

Miałem podobny problem, którego również nie mogłem rozwiązać (jest zaskakująco mało informacji o PHPUnit). W moim przypadku po prostu robiłem każdy test osobno - znane dane wejściowe i znane wyjście. Zdałem sobie sprawę, że nie muszę tworzyć pozorowanego obiektu typu jack-of-all-trade, potrzebowałem tylko konkretnego do konkretnego testu, dlatego oddzieliłem testy i mogę testować poszczególne aspekty mojego kodu jako osobne jednostka. Nie jestem pewien, czy to może mieć zastosowanie do Ciebie, czy nie, ale to zależy od tego, co musisz przetestować.

JamShady
źródło
Niestety to nie zadziałałoby w mojej sytuacji. Mock jest przekazywany do metody, którą testuję, a metoda testowa wywołuje metodę pozorowaną z różnymi argumentami. Warto wiedzieć, że nie udało się rozwiązać problemu. Wygląda na to, że może to być ograniczenie 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
źródło
-2

Próbować :

->with($this->equalTo('one'),$this->equalTo('two))->will($this->returnValue('return value'));
erenon
źródło
Ta odpowiedź nie dotyczy pierwotnego pytania, ale szczegółowo opisuje podobny problem, jaki miałem: sprawdź, czy podano określony zestaw parametrów. PHPUnit z () akceptuje wiele argumentów, po jednym dopasowaniu dla każdego parametru.
TaZ