Mockery no matching handler for closure
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);
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.