PHPUnit тестирование с закрытием
Пришло время попытаться написать тест для метода класса, который вызывает метод макета с закрытием. Как бы вы подтвердили, что закрытие называется?
Я знаю, что вы сможете утверждать, что этот параметр является экземпляром Closure
. Но как бы вы могли проверить что-нибудь о закрытии?
Например, как бы вы проверили переданную функцию:
class SUT {
public function foo($bar) {
$someFunction = function() { echo "I am an anonymous function"; };
$bar->baz($someFunction);
}
}
class SUTTest extends PHPUnit_Framework_TestCase {
public function testFoo() {
$mockBar = $this->getMockBuilder('Bar')
->setMethods(array('baz'))
->getMock();
$mockBar->expects($this->once())
->method('baz')
->with( /** WHAT WOULD I ASSERT HERE? **/);
$sut = new SUT();
$sut->foo($mockBar);
}
}
Вы не можете сравнивать два закрытия в PHP. Есть ли способ в PHPUnit выполнить параметр, переданный или каким-то образом проверить его?
Ответы
Ответ 1
Ваша проблема заключается в том, что вы не вводите свою зависимость (закрытие), которая всегда усложняет модульное тестирование и делает невозможным изоляцию.
Внесите закрытие в SUT::foo()
вместо того, чтобы создавать его внутри, и вы найдете тестирование намного проще.
Вот как я бы разработал метод (имея в виду, что я ничего не знаю о вашем реальном коде, поэтому это может быть или не быть практичным для вас):
class SUT
{
public function foo($bar, $someFunction)
{
$bar->baz($someFunction);
}
}
class SUTTest extends PHPUnit_Framework_TestCase
{
public function testFoo()
{
$someFunction = function() {};
$mockBar = $this->getMockBuilder('Bar')
->setMethods(array('baz'))
->getMock();
$mockBar->expects($this->once())
->method('baz')
->with($someFunction);
$sut = new SUT();
$sut->foo($mockBar, $someFunction);
}
}
Ответ 2
Если у закрытия есть некоторые побочные эффекты внутри SUT
, которые могут быть проверены тестом после макетного вызова, используйте returnCallback
, чтобы предоставить другое замыкание, которое вызывается с переданными аргументами, и возвращаемое значение возвращается к SUT
. Это позволит вам вызвать закрытие SUT
, чтобы вызвать побочные эффекты.
class SUT {
public function foo($bar) {
$someFunction = function() { return 5 * 3; };
return $bar->baz($someFunction);
}
}
class SUTTest extends PHPUnit_Framework_TestCase {
public function testFoo() {
$mockBar = $this->getMockBuilder('Bar')
->setMethods(array('baz'))
->getMock();
$mockBar->expects($this->once())
->method('baz')
->will($this->returnCallback(function ($someFunction) {
return $someFunction();
}));
$sut = new SUT();
self::assertEquals(15, $sut->foo($mockBar));
}
}
Ответ 3
Если вы хотите высмеять анонимную функцию (обратный вызов), вы можете высмеять класс с помощью метода __invoke
. Например:
$shouldBeCalled = $this->getMock(\stdClass::class, ['__invoke']);
$shouldBeCalled->expects($this->once())
->method('__invoke');
$someServiceYouAreTesting->testedMethod($shouldBeCalled);
Если вы используете последний PHPUnit, вам нужно будет использовать mock builder, чтобы сделать трюк:
$shouldBeCalled = $this->getMockBuilder(\stdClass::class)
->setMethods(['__invoke'])
->getMock()
$shouldBeCalled->expects($this->once())
->method('__invoke');
$someServiceYouAreTesting->testedMethod($shouldBeCalled);
Вы также можете установить ожидания для аргументов метода или задать возвращаемое значение, точно так же, как и для любого другого метода:
$shouldBeCalled->expects($this->once())
->method('__invoke')
->with($this->equalTo(5))
->willReturn(15);