¿Cómo puedo hacer que PHPUnit MockObjects devuelva diferentes valores basados ​​en un parámetro?

141

Tengo un objeto simulado PHPUnit que devuelve 'return value'sin importar sus argumentos:

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

Lo que quiero poder hacer es devolver un valor diferente en función de los argumentos pasados ​​al método simulado. He intentado 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"'));

Pero esto hace que PHPUnit se queje si no se llama al simulacro con el argumento 'two', por lo que supongo que la definición de methodToMock('two')sobrescribe la definición del primero.

Entonces mi pregunta es: ¿hay alguna forma de obtener un objeto simulado PHPUnit para devolver un valor diferente en función de sus argumentos? Y si es así, ¿cómo?

Ben Dowling
fuente

Respuestas:

125

Use una devolución de llamada. por ejemplo (directamente de la documentación de 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();
    // ...
}
?>

Realice el procesamiento que desee en la devolución de llamada () y devuelva el resultado según sus $ args, según corresponda.

Howard Sandford
fuente
2
¿Puede proporcionar un enlace a la documentación? Parece que no puedo encontrarlo con "Google"
Kris Erickson
66
Tenga en cuenta que puede usar un método como devolución de llamada pasando una matriz, por ejemplo $this->returnCallback(array('MyClassTest','myCallback')).
Patrick Fisher
1
También debería ser posible pasarle directamente un cierre
Ocramius
77
Esto debe usarse solo en casos raros. Sugeriría usar returnValueMap en su lugar, ya que no requiere que se escriba una lógica personalizada en la devolución de llamada.
Herman J. Radtke III
1
No puedo agradecerte lo suficiente. Además, con versiones PHP> 5.4, puede usar una función anónima como devolución de llamada. $this->returnCallback(function() { // ... })
bmorenate
110

De los últimos documentos de phpUnit: "A veces, un método con código auxiliar debería devolver valores diferentes según una lista predefinida de argumentos. Puede usar returnValueMap () para crear un mapa que asocie argumentos con los valores de retorno correspondientes".

$mock->expects($this->any())
    ->method('getConfigValue')
    ->will(
        $this->returnValueMap(
            array(
                array('firstparam', 'secondparam', 'retval'),
                array('modes', 'foo', array('Array', 'of', 'modes'))
            )
        )
    );
Nikola Ivancevic
fuente
3
El enlace en la publicación es antiguo, el correcto está aquí: returnValueMap ()
hejdav
49

Tuve un problema similar (aunque ligeramente diferente ... No necesitaba un valor de retorno diferente en función de los argumentos, pero tuve que probar para asegurarme de que se pasaran 2 conjuntos de argumentos a la misma función). Me topé con algo como esto:

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

No es perfecto, ya que requiere que se conozca el orden de las 2 llamadas a foo (), pero en la práctica esto probablemente no sea tan malo.

Adán
fuente
28

Probablemente desee hacer una devolución de llamada de una manera 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
fuente
16

No es exactamente lo que pides, pero en algunos casos puede ayudar:

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

onConsecutionCalls : devuelve una lista de valores en el orden especificado

Prokhor Sednev
fuente
4

Pase una matriz de dos niveles, donde cada elemento es una matriz de:

  • primero son los parámetros del método, y el menor es el valor de retorno

ejemplo:

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

También puede devolver el argumento de la siguiente manera:

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

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

Como puede ver en la documentación de Mocking , el método returnValue($index)permite devolver el argumento dado.

Gabriel Gcia Fdez
fuente
0

¿Te refieres a algo como esto?

public function TestSomeCondition($condition){
  $mockObj = $this->getMockObject();
  $mockObj->setReturnValue('yourMethod',$condition);
}
eddy147
fuente
Creo que es un código SimpleTest, no PHPUnit. Pero no, no es lo que quiero lograr. Digamos que tenía un objeto simulado que devolvía una palabra para un número dado. Mi método simulado necesitaría devolver "uno" cuando se llama con 1, "dos" cuando se llama con 2, etc. $
Ben Dowling
0

Tuve un problema similar que no pude resolver también (hay sorprendentemente poca información sobre PHPUnit). En mi caso, acabo de hacer cada prueba por separado: entrada conocida y salida conocida. Me di cuenta de que no necesitaba hacer un objeto simulado de Jack-of-All-Trades, solo necesitaba uno específico para una prueba específica, y por lo tanto separé las pruebas y puedo probar aspectos individuales de mi código como un separado unidad. No estoy seguro de si esto podría ser aplicable a usted o no, pero eso depende de lo que necesite probar.

JamShady
fuente
Desafortunadamente eso no funcionaría en mi situación. El simulacro se pasa a un método que estoy probando, y el método de prueba llama al método simulado con diferentes argumentos. Sin embargo, es interesante saber que no pudo resolver el problema. Parece que esto podría ser una limitación 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
fuente
-2

Tratar :

->with($this->equalTo('one'),$this->equalTo('two))->will($this->returnValue('return value'));
Erenon
fuente
Esta respuesta no se aplica a la pregunta original, pero detalla un problema similar que tuve: verificar que se proporcione un cierto conjunto de parámetros. PHPUnit's with () acepta múltiples argumentos, una coincidencia para cada parámetro.
TaZ