Como posso obter que o PHPUnit MockObjects retorne valores diferentes com base em um parâmetro?

141

Eu tenho um objeto simulado PHPUnit que retorna, 'return value'independentemente de seus argumentos:

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

O que eu quero poder fazer é retornar um valor diferente com base nos argumentos passados ​​para o método mock. Eu tentei algo como:

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

Mas isso faz com que o PHPUnit se queixe se o mock não for chamado com o argumento 'two', então suponho que a definição de methodToMock('two')sobrescreva a definição do primeiro.

Então, minha pergunta é: Existe alguma maneira de obter um objeto mock do PHPUnit para retornar um valor diferente com base em seus argumentos? E se sim, como?

Ben Dowling
fonte

Respostas:

125

Use um retorno de chamada. por exemplo (direto da documentação do 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();
    // ...
}
?>

Faça o processamento que quiser no retorno de chamada () e retorne o resultado com base em seus $ args, conforme apropriado.

Howard Sandford
fonte
2
Você pode fornecer um link para a documentação? Eu não consigo encontrá-lo com o "Google"
Kris Erickson
6
Observe que você pode usar um método como retorno de chamada passando uma matriz, por exemplo $this->returnCallback(array('MyClassTest','myCallback')).
Patrick Fisher
1
Também deve ser possível passar diretamente um fecho a ele
Ocramius
7
Isso deve ser usado apenas em casos raros. Eu sugeriria o uso de returnValueMap , pois não exige que a lógica personalizada seja gravada no retorno de chamada.
Herman J. Radtke III
1
Não posso agradecer o suficiente. Além disso, nas versões PHP> 5.4, você pode usar uma função anônima como retorno de chamada. $this->returnCallback(function() { // ... })
bmorenate
110

Dos documentos mais recentes do phpUnit: "Às vezes, um método stubbed deve retornar valores diferentes, dependendo de uma lista predefinida de argumentos. Você pode usar returnValueMap () para criar um mapa que associe argumentos aos valores de retorno correspondentes."

$mock->expects($this->any())
    ->method('getConfigValue')
    ->will(
        $this->returnValueMap(
            array(
                array('firstparam', 'secondparam', 'retval'),
                array('modes', 'foo', array('Array', 'of', 'modes'))
            )
        )
    );
Nikola Ivancevic
fonte
3
O link no post é antigo, o correto está aqui: returnValueMap ()
hejdav
49

Eu tive um problema semelhante (embora um pouco diferente ... eu não precisava de um valor de retorno diferente com base em argumentos, mas tive que testar para garantir que dois conjuntos de argumentos estavam sendo transmitidos para a mesma função). Eu tropecei em usar algo como isto:

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

Não é perfeito, pois exige que a ordem das 2 chamadas para foo () seja conhecida, mas na prática isso provavelmente não é tão ruim.

Adão
fonte
28

Você provavelmente desejaria fazer um retorno de chamada da maneira 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
fonte
16

Não é exatamente o que você pergunta, mas, em alguns casos, pode ajudar:

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

onConsecutiveCalls - retorna uma lista de valores na ordem especificada

Prokhor Sednev
fonte
4

Passe uma matriz de dois níveis, em que cada elemento é uma matriz de:

  • primeiro são os parâmetros do método e o mínimo é o valor de retorno.

exemplo:

->willReturnMap([
    ['firstArg', 'secondArg', 'returnValue']
])
antonmarin
fonte
2

Você também pode retornar o argumento da seguinte maneira:

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

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

Como você pode ver na documentação de zombaria , o método returnValue($index)permite retornar o argumento fornecido.

Gabriel Gcia Fdez
fonte
0

Você quer dizer algo assim?

public function TestSomeCondition($condition){
  $mockObj = $this->getMockObject();
  $mockObj->setReturnValue('yourMethod',$condition);
}
eddy147
fonte
Eu acho que é o código SimpleTest, não PHPUnit. Mas não, não é o que eu quero alcançar. Digamos que eu tenha um objeto simulado que retorne uma palavra para um determinado número. Meu método de simulação seria necessário para voltar "um" quando chamado com 1, "dois" quando chamado com 2 etc. $
Ben Dowling
0

Eu tive um problema semelhante que também não consegui resolver (há surpreendentemente pouca informação sobre o PHPUnit). No meu caso, acabei de fazer cada teste separadamente - entrada e saída conhecidas. Percebi que não precisava criar um objeto fictício de pau para toda obra, só precisava de um específico para um teste específico e, portanto, separei os testes e posso testar aspectos individuais do meu código como um separado. unidade. Não tenho certeza se isso pode ser aplicável a você ou não, mas isso depende do que você precisa testar.

JamShady
fonte
Infelizmente isso não funcionaria na minha situação. O mock está sendo passado para um método que estou testando, e o método de teste chama o método mock com argumentos diferentes. É interessante saber que você não conseguiu resolver o problema. Parece que isso pode ser uma limitação do 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
fonte
-2

Experimentar :

->with($this->equalTo('one'),$this->equalTo('two))->will($this->returnValue('return value'));
erenon
fonte
Esta resposta não se aplica à pergunta original, mas detalha um problema semelhante que tive: verifique se um determinado conjunto de parâmetros é fornecido. O PHPUnit's com () aceita vários argumentos, um correspondente para cada parâmetro.
TaZ 25/05