Как я могу заставить PHPUnit MockObjects возвращать разные значения в зависимости от параметра?

143

У меня есть фиктивный объект PHPUnit, который возвращается 'return value'независимо от аргументов:

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

Я хочу иметь возможность возвращать другое значение на основе аргументов, переданных фиктивному методу. Я пробовал что-то вроде:

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

Но это заставляет PHPUnit жаловаться, если макет не вызывается с аргументом 'two', поэтому я предполагаю, что определение of methodToMock('two')заменяет определение первого.

Итак, мой вопрос: есть ли способ заставить фиктивный объект PHPUnit возвращать другое значение на основе его аргументов? И если да, то как?

Бен Доулинг
источник

Ответы:

127

Воспользуйтесь обратным вызовом. например (прямо из документации 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();
    // ...
}
?>

Выполните любую обработку, которую хотите, в callback () и верните результат на основе ваших $ args, если это необходимо.

Говард Сэндфорд
источник
2
Можете дать ссылку на документацию? Кажется, я не могу найти его с помощью "Google"
Крис Эриксон
6
Обратите внимание, что вы можете использовать метод в качестве обратного вызова, передав массив, например $this->returnCallback(array('MyClassTest','myCallback')).
Патрик Фишер,
1
Также должна быть возможность передать закрытие напрямую
Ocramius
7
Это следует использовать только в редких случаях. Я бы предложил вместо этого использовать returnValueMap, так как он не требует написания специальной логики в обратном вызове .
Herman J. Radtke III
1
Я не могу тебя отблагодарить. Кроме того, с версиями PHP> 5.4 вы можете использовать анонимную функцию в качестве обратного вызова. $this->returnCallback(function() { // ... })
бморенат
113

Из последних документов phpUnit: «Иногда метод с заглушкой должен возвращать разные значения в зависимости от заранее определенного списка аргументов. Вы можете использовать returnValueMap () для создания карты, которая связывает аргументы с соответствующими возвращаемыми значениями».

$mock->expects($this->any())
    ->method('getConfigValue')
    ->will(
        $this->returnValueMap(
            array(
                array('firstparam', 'secondparam', 'retval'),
                array('modes', 'foo', array('Array', 'of', 'modes'))
            )
        )
    );
Никола Иванчевич
источник
3
Ссылка в посте старая, правильная здесь: returnValueMap ()
hejdav
50

У меня была аналогичная проблема (хотя и немного другая ... Мне не нужно было другое возвращаемое значение на основе аргументов, но мне пришлось протестировать, чтобы убедиться, что 2 набора аргументов передаются одной и той же функции). Я наткнулся на что-то вроде этого:

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

Это не идеально, поскольку требует, чтобы был известен порядок двух вызовов foo (), но на практике это, вероятно, не так уж плохо.

Адам
источник
28

Вероятно, вы захотите выполнить обратный вызов в стиле ООП:

<?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];
    }
}
?>
Фрэнсис Льюис
источник
16

Это не совсем то, о чем вы спрашиваете, но в некоторых случаях это может помочь:

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

onConsecutiveCalls - возвращает список значений в указанном порядке

Прохор Седнев
источник
5

Передайте двухуровневый массив, где каждый элемент является массивом:

  • сначала параметры метода, а наименьшее - возвращаемое значение.

пример:

->willReturnMap([
    ['firstArg', 'secondArg', 'returnValue']
])
антонмарин
источник
2

Вы также можете вернуть аргумент следующим образом:

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

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

Как видно из документации Mocking , метод returnValue($index)позволяет возвращать заданный аргумент.

Габриэль Гсия Фдез
источник
0

Вы имеете в виду что-то подобное?

public function TestSomeCondition($condition){
  $mockObj = $this->getMockObject();
  $mockObj->setReturnValue('yourMethod',$condition);
}
eddy147
источник
Я думаю, что это код SimpleTest, а не PHPUnit. Но нет, это не то, чего я хочу достичь. Скажем, у меня есть фиктивный объект, возвращающий слово для заданного числа. Мой метод-макет должен возвращать «один» при вызове с 1, «два» при вызове с 2 и т. Д. $
Бен Даулинг
0

У меня была аналогичная проблема, которую я тоже не мог решить (для PHPUnit информации на удивление мало). В моем случае я просто сделал каждый тест отдельным тестом - известный вход и известный выход. Я понял, что мне не нужно делать объект-имитатор на все руки, мне нужен только конкретный объект для конкретного теста, поэтому я разделил тесты и могу тестировать отдельные аспекты своего кода как отдельный Блок. Я не уверен, применимо ли это к вам или нет, но это зависит от того, что вам нужно проверить.

JamShady
источник
К сожалению, в моей ситуации это не сработало. Мок передается в метод, который я тестирую, и этот тестовый метод вызывает фиктивный метод с разными аргументами. Интересно знать, что вы не смогли решить проблему. Похоже, это может быть ограничение PHPUnit.
Бен Даулинг,
-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
источник
-2

Пытаться :

->with($this->equalTo('one'),$this->equalTo('two))->will($this->returnValue('return value'));
эренон
источник
Этот ответ не относится к исходному вопросу, но он подробно описывает аналогичную проблему, с которой я столкнулся: убедитесь, что указан определенный набор параметров. PHPUnit with () принимает несколько аргументов, по одному сопоставителю для каждого параметра.
TaZ