Вычитка не подходит для закрытия

Я не могу понять, почему я получаю эту ошибку во время этого теста. Мой тест, похоже, точно соответствует остальной части кода. Что я пропускаю?

В моем тесте я:

    $passwordBroker = m::mock('Illuminate\Auth\Reminders\PasswordBroker');
    $passwordBroker->shouldReceive('reset')
        ->once()
        ->with(
            $this->resetAttributes,
            m::on(function (\Closure $closure) {
                $this->entity
                    ->shouldReceive('setAttribute')
                    ->once()
                    ->with('password', $this->resetAttributes['password']);
                $this->entity
                    ->shouldReceive('getAttributes')
                    ->once()
                    ->andReturn($this->resetAttributes);

                $closure($this->entity, $this->resetAttributes['password']);
            })
        );

Ошибка:

Mockery\Exception\NoMatchingExpectationException: No matching handler found for Mockery_4_Illuminate_Auth_Reminders_PasswordBroker::reset(array('email'=>'[email protected]','password'=>'myTestPassword','password_confirmation'=>'myTestPassword',), Closure). Either the method was unexpected or its arguments matched no expected argument list for this method

Objects: (array (
  'Closure' => 
  array (
    'class' => 'Closure',
    'properties' => 
    array (
    ),
    'getters' => 
    array (
    ),
  ),
))

Часть моего недостатка понимания может иметь отношение к тому, что я не уверен, что Objects: array(....) есть в нижней части ошибки.

Ответ 1

TL; DR: ваш аргумент закрытия для Mockery::on должен возвращать true или false.

Более длинное объяснение:

Проблема заключается в вашем вызове Mockery::on. Этот метод принимает замыкание (или другую функцию) в качестве аргумента. Это закрытие должно возвращать true или false, в зависимости от того, удовлетворяет ли аргумент закрытию тесту.

Это было довольно запутанное объяснение, поэтому я попробую пример: -)

Рассмотрим следующее ожидание:

$mock = Mockery::mock("myclass");
$mock->shouldReceive("mymethod")
    ->with("myargument")
    ->once()
    ->andReturn("something");

Это ожидание будет выполнено, если тестируемая система (SUT) вызывает

$x = $myclass->mymethod("myargument");

и значение $x будет "чем-то".

Теперь разработчики Mockery поняли, что есть некоторые ожидания, которые они просто не могут встретить. Например (и это то, что сработало мне некоторое время), закрытие. Оказывается, что замыкание в PHP является неким сложным внутренним ресурсом, и даже если вы точно определите два замыкания, они не будут одинаковыми. Рассмотрим:

$x = function($v){ echo $v; };
$y = function($v){ echo $v; };

echo $x==$y ? "True" : "False";

будет отображать значение "False". Зачем? Из моего ограниченного понимания предмета это имеет какое-то отношение к внутреннему представлению объектов замыкания в PHP. Итак, когда вы издеваетесь над методом, который требует закрытия в качестве аргумента, нет способа удовлетворить ожидания.

Метод Mockery::on() позволяет обойти это. Используя этот метод, вы можете передать (другое) закрытие для Mockery, которое оценивается как true или false, в зависимости от того, показывают ли ваши тесты правильные аргументы. Пример:

Предположим, что myclass::mymethod требует закрытия в качестве аргумента. Следующее будет всегда терпеть неудачу, независимо от того, какое закрытие вы переходите к mymethod в SUT:

$mock = Mockery::mock("myclass");
$mock->shouldReceive("mymethod")
    ->with(function($v){ echo $v; })
    ->once()
    ->andReturn("something");

Это связано с тем, что Mockery будет сравнивать аргумент, переданный в SUT (замыкание), с закрытием, определенным выше (function($v){ echo $v; }), и этот тест завершится неудачно, даже если оба закрытия идентично определены.

Используя Mockery::on(), вы можете переписать тест следующим образом:

$mock = Mockery::mock("myclass");
$mock->shouldReceive("mymethod")
    ->with(Mockery::on(function($value){
        return is_callable($value);
    })
    ->once()
    ->andReturn("something");

Теперь, когда Mockery оценивает ожидание, он вызывает замыкание, переданное как аргумент Mockery::on(). Если он возвращает true, Mockery рассмотрит пройденное ожидание; если он возвращает false, Mockery будет считать его неудачным.

Ожидание в этом примере будет передано для закрытия any, которое передается в myclass::mymethod, что, вероятно, недостаточно специфично. Вероятно, вам нужен более сложный тест, но основная идея.