Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mockery no matching handler for closure

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.

like image 749
Webnet Avatar asked Apr 08 '14 19:04

Webnet


2 Answers

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.

like image 196
Kryten Avatar answered Oct 19 '22 01:10

Kryten


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);

like image 43
Joel Harkes Avatar answered Oct 19 '22 00:10

Joel Harkes