Mockery no matching handler for closure
Asked Answered
F

2

20

I can't figure out why I'm getting this error during this test. My test appears to be matching the rest of the code exactly. What am I overlooking?

In my test I have:

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

The error:

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 (
    ),
  ),
))

Part of my lack of understanding may have to do with the fact that I'm not sure what the Objects: array(....) is that appears at the bottom of the error.

Fancied answered 8/4, 2014 at 19:20 Comment(0)
J
57

TL;DR: your closure argument to Mockery::on needs to return true or false.

The longer explanation:

The problem is with your call to Mockery::on. This method takes a closure (or other function) as an argument. That closure should return true or false, depending on whether the argument to the closure satisfies the test.

That was a rather confusing explanation, so I'll try an example :-)

Consider the following expectation:

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

This expectation will be met if the system under test (SUT) calls

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

and the value of $x will be "something".

Now the developers of Mockery realized that there are some expectations that they simply cannot meet. For example (and this is something that tripped me up for a while), a closure. It turns out that a closure in PHP is some kind of complicated internal resource, and even if you define two closures identically, they will not be the same. Consider:

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

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

will echo the value "False". Why? From my limited understanding of the subject, it has something to do with the internal representation of closure objects in PHP. So, when you're mocking a method that requires a closure as an argument, there is no way to satisfy the expectation.

The Mockery::on() method provides a way around this. Using this method, you can pass a (different) closure to Mockery that evaluates to true or false, depending on whether your tests show that you have the right arguments. An example:

Imagine that myclass::mymethod requires a closure as an argument. The following will always fail, regardless of what closure you pass to mymethod in the SUT:

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

This is because Mockery will compare the argument passed in the SUT (a closure) to the closure defined above (function($v){ echo $v; }) and that test will fail, even if the two closures are identically defined.

Using Mockery::on(), you can rewrite the test as follows:

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

Now when Mockery evaluates the expectation, it will call the closure passed as the argument to Mockery::on(). If it returns true, Mockery will consider the expectation passed; if it returns false, Mockery will consider it as having failed.

The expectation in this example will pass for any closure that is passed to myclass::mymethod, which is probably not specific enough. You probably want a more sophisticated test, but that's the basic idea.

Jacynth answered 8/4, 2014 at 21:44 Comment(5)
/facepalm .... I know it needs to return true or false and completely excluded it. You're totally right! Thanks for the detailed explination, +1!Fancied
I've been stumped by this same problem several times in recent weeks. I think I'll bookmark this so I don't have to keep re-solving it too :-)Jacynth
"Why? From my limited understanding of the subject, it has something to do with the internal representation of closure objects in PHP" < Checking whether two programs are equal is a famous problem in theoretical computer science and known to be impossible in general.Tuinal
Thanks, you saved me! For dummies like me, use need one "Mock::on" per argument!Hypochondriac
Thanks a bunch on the closure fix!Purslane
G
5

If you are where like me not figuring out why it throws errors, you need to replace ->with() into ->withArgs(), when you have multiple function parameters.

working version

$mock->shouldReceive('functionWithMoreParams')->withArgs(fn($arg1, $arg2) => true);

Throwing error

$mock->shouldReceive('functionWithMoreParams')->with(fn($$arg1, $arg2) => true);

Gaddi answered 13/5, 2020 at 10:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.