Laravel 9 introduced a new way to create custom validation rules: https://laravel.com/docs/master/validation#using-rule-objects. It allows you to write a custom validation rule with only one method: __invoke.
This way of writing custom validation rules will become the default in Laravel 10.
Here's the custom expectation I came up with: expect()->extend('toPassWith', function(mixed $value) { $rule = $this->value; if (! $rule instanceof InvokableRule) { throw new Exception('Value is not an invokable rule'); } $passed = true; $fail = function() use (&$passed) { $passed = false; }; $rule('attribute', $value, $fail); expect($passed)->toBeTrue(); }); In this custom expectation, we'll invoke our rule and pass our own $fail closure to it.
Using this custom expectation, testing custom rules becomes easy.