Тестирование правил валидации Laravel с помощью Pest PHP
Необходимые условия
В данном руководстве мы будем тестировать правила валидации для регистрации пользователей. Однако концепция применима и к другим сценариям. Предполагается, что:
- Вы знаете, как настроить приложение Laravel. Если нет, то вы можете обратиться к официальной документации Laravel
- Вы знакомы с фреймворком тестирования Pest PHP. Если нет, обратитесь к документации по 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')],
]);
name not too long
— необязательное имя набора данных, удобное для человека. Я использую его для улучшения читабельности вывода cli.validation-rules
— имя набора данных.fn() => getLongName()
рекомендуется использовать функцию замыкание, когда вы получаете данные, связанные с вычислениями или базой данных.__(key: 'validation.custom.name.max')
короткая функция Laravel для получения перевода. С её помощью можно проверить, что пользователю возвращается правильное сообщение.
Настройка сообщений валидации
Хотя настройка сообщений об ошибках не является обязательной, я, как UI/UX-дизайнер, понимаю необходимость чётких, лаконичных и полезных сообщений об ошибках.
Заключение
Наборы данных Pest являются отличным инструментом для тестирования правил валидации Laravel. Я надеюсь, что вы узнали кое-что новое о тестировании правил валидации. Исходный код этого руководства можно получить из моего репозитория GitHub
Похожие статьи
- Архитектурный плагин Pest
- Laravel: Руководство по тестированию с Pest
- Тестирование конечных точек JSON:API с PestPHP
- Laravel: Как тестировать invokable правила