Можно ли настроить PHPUnit таким образом?
$context = $this->getMockBuilder('Context') ->getMock(); $context->expects($this->any()) ->method('offsetGet') ->with('Matcher') ->will($this->returnValue(new Matcher())); $context->expects($this->any()) ->method('offsetGet') ->with('Logger') ->will($this->returnValue(new Logger()));
Я использую PHPUnit 3.5.10, и он терпит неудачу, когда я запрашиваю Matcher, потому что он ожидает аргумент «Logger». Это похоже на то, что второе ожидание переписывает первый, но когда я отказываюсь от макета, все выглядит нормально.
Начиная с PHPUnit 3.6 существует $this->returnValueMap()
который может быть использован для возврата разных значений в зависимости от заданных параметров к методу stub.
К сожалению, это невозможно при использовании PHPUnit Mock API по умолчанию.
Я вижу два варианта, которые могут приблизить вас к чему-то вроде этого:
$context = $this->getMockBuilder('Context') ->getMock(); $context->expects($this->at(0)) ->method('offsetGet') ->with('Matcher') ->will($this->returnValue(new Matcher())); $context->expects($this->at(1)) ->method('offsetGet') ->with('Logger') ->will($this->returnValue(new Logger()));
Это будет работать нормально, но вы тестируете больше, чем должны (в основном, сначала его вызывают с помощью соединителя, и это деталь реализации).
Также это не удастся, если у вас есть несколько вызовов для каждой из функций!
Это больше работает, но работает лучше, поскольку вы не зависите от порядка вызовов:
<?php class FooTest extends PHPUnit_Framework_TestCase { public function testX() { $context = $this->getMockBuilder('Context') ->getMock(); $context->expects($this->exactly(2)) ->method('offsetGet') ->with($this->logicalOr( $this->equalTo('Matcher'), $this->equalTo('Logger') )) ->will($this->returnCallback( function($param) { var_dump(func_get_args()); // The first arg will be Matcher or Logger // so something like "return new $param" should work here } )); $context->offsetGet("Matcher"); $context->offsetGet("Logger"); } } class Context { public function offsetGet() { echo "org"; } }
Это приведет к выводу:
/* $ phpunit footest.php PHPUnit 3.5.11 by Sebastian Bergmann. array(1) { [0]=> string(7) "Matcher" } array(1) { [0]=> string(6) "Logger" } . Time: 0 seconds, Memory: 3.00Mb OK (1 test, 1 assertion)
Я использовал $this->exactly(2)
в матчи, чтобы показать, что это также работает с подсчетом вызовов. Если вам не нужна эта замена для $this->any()
, конечно, будет работать.
Вы можете добиться этого с помощью обратного вызова:
class MockTest extends PHPUnit_Framework_TestCase { /** * @dataProvider provideExpectedInstance */ public function testMockReturnsInstance($expectedInstance) { $context = $this->getMock('Context'); $context->expects($this->any()) ->method('offsetGet') // Accept any of "Matcher" or "Logger" for first argument ->with($this->logicalOr( $this->equalTo('Matcher'), $this->equalTo('Logger') )) // Return what was passed to offsetGet as a new instance ->will($this->returnCallback( function($arg1) { return new $arg1; } )); $this->assertInstanceOf( $expectedInstance, $context->offsetGet($expectedInstance) ); } public function provideExpectedInstance() { return array_chunk(array('Matcher', 'Logger'), 1); } }
Должен пройти для любых аргументов «Logger» или «Matcher», offsetGet
методу offsetGet
контекстного offsetGet
:
F:\Work\code\gordon\sandbox>phpunit NewFileTest.php PHPUnit 3.5.13 by Sebastian Bergmann. .. Time: 0 seconds, Memory: 3.25Mb OK (2 tests, 4 assertions)
Как вы можете видеть, PHPUnit выполнил два теста. Один для каждого значения dataProvider. И в каждом из этих тестов он сделал утверждение для with()
и одно для instanceOf
, следовательно, четыре утверждения.
Следуя от ответа @edorian и комментариям (@MarijnHuizendveld) относительно обеспечения того, чтобы метод вызывался как с Matcher, так и с Logger, а не просто дважды с помощью Matcher или Logger, вот пример.
$expectedArguments = array('Matcher', 'Logger'); $context->expects($this->exactly(2)) ->method('offsetGet') ->with($this->logicalOr( $this->equalTo('Matcher'), $this->equalTo('Logger') )) ->will($this->returnCallback( function($param) use (&$expectedArguments){ if(($key = array_search($param, $expectedArguments)) !== false) { // remove called argument from list unset($expectedArguments[$key]); } // The first arg will be Matcher or Logger // so something like "return new $param" should work here } )); // perform actions... // check all arguments removed $this->assertEquals(array(), $expectedArguments, 'Method offsetGet not called with all required arguments');
не$expectedArguments = array('Matcher', 'Logger'); $context->expects($this->exactly(2)) ->method('offsetGet') ->with($this->logicalOr( $this->equalTo('Matcher'), $this->equalTo('Logger') )) ->will($this->returnCallback( function($param) use (&$expectedArguments){ if(($key = array_search($param, $expectedArguments)) !== false) { // remove called argument from list unset($expectedArguments[$key]); } // The first arg will be Matcher or Logger // so something like "return new $param" should work here } )); // perform actions... // check all arguments removed $this->assertEquals(array(), $expectedArguments, 'Method offsetGet not called with all required arguments');
Это с PHPUnit 3.7.
Если метод, который вы тестируете, фактически ничего не возвращает, и вам просто нужно проверить, что он вызывается с правильными аргументами, применяется тот же подход. Для этого сценария я также попытался сделать это, используя функцию обратного вызова для $ this-> callback в качестве аргумента для с, а не returnCallback в завещании. Это терпит неудачу, поскольку внутренне phpunit вызывает обратный вызов дважды в процессе проверки обратного вызова совпадения аргументов. Это означает, что подход завершается неудачно, поскольку во втором вызове этот аргумент уже удален из массива ожидаемых аргументов. Я не знаю, почему phpunit называет это дважды (кажется ненужной тратой), и я думаю, вы могли бы обойти это, удалив его только во втором вызове, но я был недостаточно уверен, что это предполагаемое и последовательное поведение phpunit для полагайтесь на происходящее.
Я просто наткнулся на это расширение PHP, чтобы имитировать объекты: https://github.com/etsy/phpunit-extensions/wiki/Mock-Object
Мои 2 цента на эту тему: обратите внимание при использовании в ($ x): это означает, что ожидаемый вызов метода будет вызовом метода ($ x + 1) th на макет-объекте; это не означает, что будет ($ x + 1) -й вызов ожидаемого метода. Это заставило меня потратить немного времени, поэтому я надеюсь, что это не с вами. С уважением к каждому.