Laravel: Как тестировать invokable правила

Источник: «How to test Laravel's invokable rules»
Laravel 9 представил новый способ создания пользовательских правил валидации: вызываемые правила (invokable rules). Это позволяет написать пользовательское правило валидации только с одним методом: __invoke.

Вот простой пример:

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\InvokableRule;

class UppercaseRule implements InvokableRule
{
public function __invoke(string $attribute, mixed $value, Closure $fail): void
{
if (strtoupper($value) !== $value) {
$fail('The :attribute must be uppercase.');
}
}
}

Если хотите, чтобы правило не прошло валидацию, он должно выполнить переданное Замыкание $fail и выдать сообщение об ошибке валидации.

Этот способ написания пользовательских правил валидации станет стандартным в Laravel 10. (Если вы пишете свои классы валидации используя старый синтаксис, не беспокойтесь, он всё ещё будет поддерживаться в Laravel 10).

Когда я написал свой первый вызываемый объект, я думал, как его протестировать. Придумал хорошее решение (спасибо Оливеру Найборо за подсказку правильного направления)

Сейчас я предпочитаю тестовый фреймворк Pest. В Pest есть способ написания пользовательского ожидания.

Вот пользовательское ожидание, которое я придумал:

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();
});

В это пользовательском ожидании мы будем вызывать наше правило и передавать ему наше собственное замыкание $fail. Используя переменную $passed, мы определяем была ли выполнена $fail. Заметили, что &$passed? Здесь мы передаём переменную $passed по ссылке поэтому когда она изменится внутри замыкания, она так же изменится в нашем пользовательском ожидании.

Используя это пользовательское ожидание, тестирование пользовательских правил становится простым. Давайте посмотрим на это в действии.

it('will only accept fully uppercased values', function() {
$rule = new UppercaseRule();

expect($rule)->toPassWith('HELLO');

// используя not() you вы можете инвертировать ожидание
expect($rule)->not()->toPassWith('hello');

// вы можете связать ожидания
expect($rule)
->toPassWith('ANOTHER')
->toPassWith('TEST');
});

В реальном тестировании вы можете захотеть добавить больше случаев для проверки этого конкретного правила, но я надеюсь, что этот пример хорошо показывает, что toPassWith делает тестирование вызываемых правил очень простым.

Мне особенно нравится, что наш тест написан на простом английском языке использующим этот синтаксис.

expect($rule)->toPassWith('HELLO');

Круто, правда?

Если хотите использовать это ожидание в своих тестах, можете выбрать наш новый пакет spatie/pest-expectations, который содержит пару удобных опциональных пользовательских ожиданий.

Дополнительные материалы

Предыдущая Статья

Eloquent: Оптимизация подсчёта Моделей по Отношениям

Следующая Статья

Laravel: Ошибки связанные с мутабельностью Carbon