I have previously been writing custom Laravel validation rules with explicit passes()
and message()
methods. However, since Laravel 9, the preferred method is now a little different. The documentation shows making an invokable rule. This syntax is nice and short, but I had to figure out how to test them.
I was able to build a test which successfully asserted when the rule failed, but I couldn't work out a solution for when the rule passed. When an invokable rule passes, nothing is returned from the method itself nor is the $fail
closure called. Luckily I came across a post by Freek Van der Herten called How to test Laravel's invokable rules which pointed me in a useful direction. Freek’s article covers using the Pest syntax, but I needed something for PHPUnit.
This is what I came up with;
<?php
namespace Tests\Unit\Rules;
use App\Rules\CustomRule as Rule;
use Illuminate\Contracts\Validation\InvokableRule;
use Tests\Unit\TestCase;
/**
* @group rule
* @group custom-rule
*/
class CustomRuleTest extends TestCase
{
protected Rule $rule;
/** @test */
public function implementsClasses(): void
{
$contracts = class_implements($this->rule::class);
$this->assertArrayHasKey(InvokableRule::class, $contracts);
}
/** @test */
public function passes(): void
{
$passes = true;
$attribute = 'email';
$value = $this->faker->safeEmail();
$this->app->call($this->rule, [
'value' => $value,
'attribute' => $attribute,
'fail' => static function () use (&$passes): void {
$passes = false;
},
]);
$this->assertTrue($passes);
}
/** @test */
public function fails(): void
{
$passes = true;
$attribute = 'email';
$value = $this->faker->sentence();
$this->app->call($this->rule, [
'value' => $value,
'attribute' => $attribute,
'fail' => function (string $message) use (&$passes): void {
$passes = false;
$this->assertSame('Sorry, the validation rule has failed.', $message);
},
]);
$this->assertFalse($passes);
}
protected function setUp(): void
{
parent::setUp();
$this->rule = new Rule();
}
}