Use custom Mockery matchers for better Laravel assertions

Clean up your tests, especially when passing models as arguments

Joel Clermont
Joel Clermont
2023-10-11

Let's say you're mocking a method that accepts an Eloquent model as an argument. Because of the way Eloquent models work, you can't just do a normal comparison on the two different model instances.

Laravel does provide a useful is() method to make this comparison easier, but you need to do something like this to use it with Mockery in your tests:

// $customer is a User model defined earlier in the test...

$stripeService = $this->mock(StripeService::class);
$stripeService->shouldReceive('createCustomer')
    ->with(
        \Mockery::on(function (User $user) use ($customer) {
            return $user->is($customer);
        }),
    )
    ->once()
    ->andReturn('cus_some_id_xyz');

It works, and it's fine, but it's not great. A better way is to define a custom matcher class which wraps up that is() check for us:

// You can put this in a `tests/Matchers` directory
class EloquentModelMatcher extends MatcherAbstract
{
    public function match(&$actual)
    {
        return $this->_expected->is($actual);
    }

    public function __toString()
    {
        return '<Model>';
    }
}

And then, in your base test setup, you register the custom matcher for all Eloquent model classes:

protected function setUp(): void
{
    parent::setUp();

    \Mockery::getConfiguration()->setDefaultMatcher(
        class: \Illuminate\Database\Eloquent\Model::class,
        matcherClass: \Tests\Matchers\EloquentModelMatcher::class,
    );
}

With that matcher configuration in place, any time Mockery tries to match against an Eloquent model, it will use your custom matcher class. Now the test gets much cleaner:

$stripeService = $this->mock(StripeService::class);
$stripeService->shouldReceive('createCustomer')
            ->with($customer)
            ->once()
            ->andReturn('cus_some_id_xyz');

If you go looking in the Mockery docs, you won't find this mentioned, but Filip mentioned it in a very helpful tweet and I just got around to trying it out.

One final note: this was added in Mockery v1.4.4, so if it isn't working for you, make sure you're at least on that version.

Here to help,

Joel

P.S. Ever wonder if your Laravel app could be more secure? Download our free book for 7 steps to review.

Toss a coin in the jar if you found this helpful.
Want a tip like this in your inbox every weekday? Sign up below 👇🏼

Level up your Laravel skills!

Each 2-minute email has real-world advice you can use.