Тесты: фабрики, подмены и Pest
Настоящий тест не должен отправлять письма, списывать деньги и зависеть от чужих сервисов. В Laravel для этого есть фабрики, создающие данные одной строкой, и подмены, которые перехватывают письма, задания и запросы и позволяют проверить, что они были отправлены.
Фабрики
// database/factories/PostFactory.php
public function definition(): array
{
return [
'user_id' => User::factory(),
'title' => fake()->sentence(),
'body' => fake()->paragraphs(3, true),
'published_at' => now(),
];
}
public function draft(): static
{
return $this->state(['published_at' => null]);
}$post = Post::factory()->create();
$drafts = Post::factory()->count(3)->draft()->create();
$mine = Post::factory()->for($user)->create(['title' => 'Моя статья']);Метод draft() — состояние: именованный вариант данных.
Чистая база
Трейт RefreshDatabase возвращает базу в исходное состояние после каждого теста:
use Illuminate\Foundation\Testing\RefreshDatabase;
class PostTest extends TestCase
{
use RefreshDatabase;
public function test_guest_does_not_see_drafts(): void
{
Post::factory()->draft()->create(['title' => 'Секрет']);
$this->get('/posts')->assertOk()->assertDontSee('Секрет');
}
}Вошедший пользователь
$user = User::factory()->create();
$this->actingAs($user)
->post('/posts', ['title' => 'Новая статья', 'body' => 'Текст'])
->assertRedirect();
$this->assertDatabaseHas('posts', ['title' => 'Новая статья', 'user_id' => $user->id]);Подмена почты
use Illuminate\Support\Facades\Mail;
Mail::fake();
$this->post('/orders', $data);
Mail::assertSent(OrderShipped::class, fn ($mail) => $mail->hasTo('anna@example.com'));Письмо не уходит, но тест знает, что его попытались отправить.
Другие подмены
| Подмена | Что проверяет |
|---|---|
Queue::fake() | Queue::assertPushed(ProcessVideo::class) |
Notification::fake() | Notification::assertSentTo($user, InvoicePaid::class) |
Event::fake() | Event::assertDispatched(OrderPlaced::class) |
Storage::fake('public') | Storage::disk('public')->assertExists('a.jpg') |
Http::fake() | Http::assertSent(...) |
Загрузка файла
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
Storage::fake('public');
$this->actingAs($user)->post('/avatar', [
'avatar' => UploadedFile::fake()->image('me.jpg', 200, 200),
])->assertRedirect();
Storage::disk('public')->assertExists('avatars/' . $user->id . '.jpg');Время
$this->travelTo(now()->addDays(31));
$this->assertTrue($subscription->fresh()->isExpired());Pest
Pest — другой способ записи тех же тестов, без классов:
// tests/Feature/PostTest.php
use App\Models\Post;
it('shows published posts', function () {
$post = Post::factory()->create();
$this->get('/posts')->assertOk()->assertSee($post->title);
});
it('counts words', function () {
expect(str('one two three')->wordCount())->toBe(3);
});Новые проекты Laravel предлагают выбрать Pest или PHPUnit при создании.
Запуск
php artisan test
php artisan test --filter=PostTest
php artisan test --parallelЧастая ошибка
Подмену вызывают до действия, а не после. Если написать Mail::fake() после запроса, письмо уже уйдёт по-настоящему, а проверка assertSent ничего не найдёт. И не подменяйте то, что проверяете: тест с Event::fake() не выполнит слушателей, так что их работу он не проверяет.