This post covers one of the ways you can test custom validation rules in Laravel. It assumes you’re running a Laravel version no older than 10.x. I prefer this way of testing as it’s the cleanest and more concise of others you can find online.
I will be using the custom rule provided in the official documentation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ./app/Rules/Uppercase.php
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class Uppercase implements ValidationRule
{
/**
* Run the validation rule.
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (strtoupper($value) !== $value) {
$fail('The :attribute must be uppercase.');
}
}
}
This is a very simple and straightforward rule that tests a field to see if it contains only uppecase letters. It doesn’t take into account any other data in the request (or elsewhere). If you’ve worked with older Laravel versions, you will notice that the old passes
and message
have been replaced by a single validate()
method’s that doesn’t return anything (but voidness).
A test class for the code above looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// ./tests/Unit/Rules/Uppercase.php
<?php
namespace Tests\Unit\Rules;
use Tests\TestCase;
use App\Rules\Uppercase;
class UppercaseTest extends TestCase {
const VALID_STRINGS = [
'I1M UP3RC4SE',
'I AM UPPERCASE',
'0TH3R_UPP3RC4S3'
];
const INVALID_STRINGS = [
'I MiGhT BE UPP3R',
'bUt I am Not'
];
/**
* @test
*/
public function validation_passes_for_valid_strings() {
$rulew = new Uppercase();
foreach(self::VALID_STRINGS as $string) {
$rule->validate('name', $string, function () use ($string) {
$this->assertTrue(
false,
sprintf("String is not uppercase only: %s", $string)
);
});
}
$this->asserTrue(true);
}
/**
* @test
*/
public function validation_fails_for_invalid_strings() {
$rulew = new Uppercase();
$timesFailed = 0;
foreach(self::INVALID_STRINGS as $string) {
$rule->validate('name', $string, function () use ($string, &$timesFailed) {
$timesFailed++;
});
}
$this->assertEquals(
count(self::INVALID_STRINGS),
$timesFailed,
'All strings must fail validation'
);
}
}
The test validation_passes_for_valid_strings() creates an instance of our custom rule and then iterates all strings inside the VALID_STRINGS
constant. On each iteration we call the Uppercase::validate
method and (here comes the cool part) as a closure we’re telling our rule assert an always failing comparison and log our failing $string
.
Additionally, we are asserting a seemingly redundant check true===true
to avoid getting this message in phpunit:
! validation passes for valid strings → This test did not perform any assertions 0.50s
Tests: 1 risky (0 assertions)
The other test validation_fails_for_invalid_strings() counts the number of fails and asserts the number is equal to the total strings inside INVALID_STRINGS
.
And that’s it!
Stay humble,
Iv