Подтвердить что ты не робот

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 выполнить параметр, переданный или каким-то образом проверить его?

4b9b3361

Ответ 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);