Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Тесты: фабрики, подмены и 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() не выполнит слушателей, так что их работу он не проверяет.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.