Тестирование правил валидации Laravel с помощью Pest PHP

Источник: «Testing Laravel Validation Rules with Pest PHP»
Узнайте, как тестировать правила валидации Laravel с помощью Pest PHP.

Необходимые условия

В данном руководстве мы будем тестировать правила валидации для регистрации пользователей. Однако концепция применима и к другим сценариям. Предполагается, что:

Зачем тестировать правила валидации

Тестирование регистрации пользователей

Обновите PHPUnit

Я предпочитаю использовать базу данных sqlite :memory: при тестировании своих приложений из-за простоты. Это не только ускоряет выполнение тестов, но и не требует дополнительной настройки базы данных. Обновите файл phpunit.xml следующим образом.

-<!-- <env name="DB_CONNECTION" value="sqlite"/> -->
-<!-- <env name="DB_DATABASE" value=":memory:"/> -->
+<env name="DB_CONNECTION" value="sqlite"/>
+<env name="DB_DATABASE" value=":memory:"/>

Чтобы обеспечить автоматическую миграцию базы данных во время тестирования, обновите файл Pest.php

 uses(
Tests\TestCase::class,
-// Illuminate\Foundation\Testing\RefreshDatabase::class,
+Illuminate\Foundation\Testing\LazilyRefreshDatabase::class,
)->in('Feature');

Логика регистрации

Предположим, у вас есть маршрут регистрации,

use App\Http\Controllers\RegistrationController;

Route::view('/','welcome')->name('home');
Route::post('/register', RegistrationController::class)->name('register');

Маршрут указывает на ваш контроллер, который проверяет запрос, создаёт нового пользователя и перенаправляет его на домашнюю страницу:

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;

class RegistrationController
{
public function __invoke(Request $request)
{
$validated = $request->validate([
'name' => ['required', 'string', 'min:4', 'max:80'],
'email' => ['required', 'email', 'max:100', 'unique:users'],
'password' => ['required', Password::min(size: 8)->uncompromised(), 'max:64'],
]);

$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
]);

session()->flash(
key: 'status',
value: __(
key: 'messages.user.created',
replace: ['name' => $user->name]
)
);

return to_route(route: 'home');
}
}

Где находится содержимое файлов lang/en/messages.php

<?php

declare(strict_types=1);

return [
'user' => [
'created' => "Welcome :name! You're now a member of our community."
]
];

Тестирование с помощью наборов данных pest php

Начните с создания нового функционального теста, используя приведённую ниже команду. Новый файл должен быть создан в папке tests/Feature/Feature/RegistrationControllerTest.php

php artisan make:test Feature/RegistrationControllerTest --pest

Наиболее простым способом проверки нескольких валидаций является использование наборов данных pest php. Код для файла RegistrationControllerTest.php будет выглядеть следующим образом.

<?php

use App\Models\User;
use Illuminate\Support\Str;

function getLongName(): string
{
return Str::repeat(string: 'name', times: rand(min: 30, max: 50));
}

function getATakenEmail(): string
{
$takenEmail = 'taken@example.com';
User::factory()->create(['email' => $takenEmail]);
return $takenEmail;
}

dataset(name: 'validation-rules', dataset: [
'name is required' => ['name', '', fn() => __(key: 'validation.custom.name.required')],
'name be a string' => ['name', ['array'], fn() => __(key: 'validation.custom.name.string')],
'name not too short' => ['name', 'ams', fn() => __(key: 'validation.custom.name.min')],
'name not too long' => ['name', getLongName(), fn() => __(key: 'validation.custom.name.max')],

'email is required' => ['email', '', fn() => __(key: 'validation.custom.email.required')],
'email be valid' => ['email', 'esthernjerigmail.com', fn() => __(key: 'validation.custom.email.email')],
'email not too long' => ['email', fn() => getLongName() . '@gmail.com', fn() => __(key: 'validation.custom.email.max')],
'email be unique' => ['email', fn() => getATakenEmail(), fn() => __(key: 'validation.custom.email.unique')],

'password is required' => ['password', '', fn() => __(key: 'validation.custom.password.required')],
'password be >=8 chars' => ['password', 'Hf^gsg8', fn() => __(key: 'validation.custom.password.min')],
'password be uncompromised' => ['password', 'password', 'The given password has appeared in a data leak. Please choose a different password.'],
'password not too long' => ['password', fn() => getLongName(), fn() => __(key: 'validation.custom.password.max')],
]);


it(
description: 'can validate user inputs',
closure: function (string $field, string|array $value, string $message) {

$data = [
'name' => fake()->name(),
'email' => fake()->unique()->email(),
'password' => fake()->password(minLength: 8),
];

$response = $this->post(
uri: route(name: 'register'),
data: [...$data, $field => $value]
);

$response->assertSessionHasErrors(keys: [$field => $message]);

$this->assertGuest();
})->with('validation-rules');

Примечание: Порядок правил валидации совпадает с порядком в файле RegistrationController.php

Я хочу настроить сообщение о валидации, создав файл lang/en/validation.php и добавив в него следующее содержимое:

<?php

declare(strict_types=1);

return [

'custom' => [
'name' => [
'required' => 'Please enter your name.',
'string' => 'Your name is missing.',
'min' => 'Name is too short. Try your first and last name.',
'max' => 'Name is too long. Please shorten your name and try again.',
],
'email' => [
'required' => 'Email address is required.',
'email' => 'Enter a valid email e.g yourname@gmail.com.',
'max' => 'Email is too long. Please shorten your email and try again.',
'unique' => 'Email is already registered. Try another one or reset password.',
],
'password' => [
'required' => 'Enter a password.',
'min' => 'Password should be at least 8 characters. Add a word or two.',
'max' => 'Password needs to be less than 128 characters. Please enter a short one.'
],
],
];

Выходные данные тестирования

Запустите тест, выполнив команду

vendor/bin/pest --filter="RegistrationControllerTest"

Выходные данные должны выглядеть следующим образом:

❯ vendor/bin/pest --filter="RegistrationControllerTest"

PASS Tests\Feature\Feature\RegistrationControllerTest
✓ it can validate user inputs with dataset "name is required" 1.07s
✓ it can validate user inputs with dataset "name be a string" 0.72s
✓ it can validate user inputs with dataset "name not too short" 0.77s
✓ it can validate user inputs with dataset "name not too long" 0.71s
✓ it can validate user inputs with dataset "email is required" 0.76s
✓ it can validate user inputs with dataset "email be valid" 0.73s
✓ it can validate user inputs with dataset "email not too long" 0.70s
✓ it can validate user inputs with dataset "email be unique" 0.80s
✓ it can validate user inputs with dataset "password is required" 0.09s
✓ it can validate user inputs with dataset "password be >=8 chars" 0.10s
✓ it can validate user inputs with dataset "password be uncompromised" 0.70s
✓ it can validate user inputs with dataset "password not too long" 0.75s

Tests: 12 passed (36 assertions)
Duration: 8.15s

Пояснения к коду

О функциях getLongName() и getATakenEmail()

Это пользовательские функции хэлперы, созданные мной для упрощения кода. Они многократно используются и удобны. Подробнее о хелперах можно узнать из документации по pest

Структура наборов данных

Рассмотрим следующее подмножество набора данных

dataset(name: 'validation-rules', dataset: [
'name not too long' => ['name', fn() => getLongName(), fn() => __(key: 'validation.custom.name.max')],
]);

Настройка сообщений валидации

Хотя настройка сообщений об ошибках не является обязательной, я, как UI/UX-дизайнер, понимаю необходимость чётких, лаконичных и полезных сообщений об ошибках.

Заключение

Наборы данных Pest являются отличным инструментом для тестирования правил валидации Laravel. Я надеюсь, что вы узнали кое-что новое о тестировании правил валидации. Исходный код этого руководства можно получить из моего репозитория GitHub

Похожие статьи

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

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

Как добавить CSS-анимацию раскрытия к изображениям

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

Фильтрация типов значений TypeScript