PestとはLaravelのモダンなテストフレームワーク
PestはPHPUnitの上に構築されたテストフレームワークで、より少ない記述量でわかりやすいテストが書けるように設計されています。Laravel 11のスターターキットではPestがデフォルトになっています。
PHPUnitとPestの比較
// PHPUnit:クラスベース
class UserTest extends TestCase
{
public function test_ユーザーは管理者かどうか確認できる(): void
{
$user = User::factory()->create(['role' => 'admin']);
$this->assertTrue($user->isAdmin());
}
}
// Pest:関数ベース(より少ない記述量)
test('ユーザーは管理者かどうか確認できる', function () {
$user = User::factory()->create(['role' => 'admin']);
expect($user->isAdmin())->toBeTrue();
});
Pestのインストール
composer require pestphp/pest --dev --with-all-dependencies
php artisan pest:install
# プラグイン
composer require pestphp/pest-plugin-laravel --dev
テストの書き方
基本的なアサーション
// expect() チェーン
test('商品の価格は正の整数である', function () {
$product = Product::factory()->create(['price' => 5000]);
expect($product->price)
->toBeInt()
->toBeGreaterThan(0)
->toBeLessThanOrEqual(999999);
});
// 複数の値をまとめてアサート
test('ユーザー情報が正しく登録される', function () {
$user = User::factory()->create([
'name' => '山田太郎',
'email' => 'yamada@example.com',
]);
expect($user)
->name->toBe('山田太郎')
->email->toBe('yamada@example.com')
->email_verified_at->toBeNull();
});
HTTPテスト
// Laravelのテストヘルパーとの組み合わせ
test('お問い合わせフォームが正常に送信される', function () {
$response = $this->post('/contact', [
'name' => '山田太郎',
'email' => 'yamada@example.com',
'subject' => 'テスト',
'body' => 'テストメッセージです。',
'agreed' => '1',
]);
$response
->assertStatus(302)
->assertRedirect('/contact/complete');
expect(Contact::count())->toBe(1);
});
test('未認証ユーザーはダッシュボードにアクセスできない', function () {
$this->get('/dashboard')->assertRedirect('/login');
});
test('認証済みユーザーはダッシュボードにアクセスできる', function () {
$user = User::factory()->create();
$this->actingAs($user)
->get('/dashboard')
->assertOk()
->assertViewIs('dashboard');
});
describe でテストをグループ化
describe('注文の管理', function () {
beforeEach(function () {
$this->admin = User::factory()->admin()->create();
$this->order = Order::factory()->pending()->create();
});
test('注文一覧を取得できる', function () {
$this->actingAs($this->admin)
->get('/admin/orders')
->assertOk()
->assertSee($this->order->order_number);
});
test('注文を確定できる', function () {
$this->actingAs($this->admin)
->patch("/admin/orders/{$this->order->id}/confirm")
->assertRedirect();
expect($this->order->fresh()->status)->toBe('confirmed');
});
test('既に確定済みの注文は再確定できない', function () {
$confirmed = Order::factory()->confirmed()->create();
$this->actingAs($this->admin)
->patch("/admin/orders/{$confirmed->id}/confirm")
->assertStatus(422);
});
});
データセット(Data-Driven Testing)
// 複数のデータでテストを繰り返す
dataset('無効なメールアドレス', [
['invalid'],
['@example.com'],
['user@'],
['user @example.com'],
[''],
]);
test('無効なメールアドレスはバリデーションエラーになる', function (string $email) {
$this->post('/contact', ['email' => $email])
->assertSessionHasErrors('email');
})->with('無効なメールアドレス');
モック・スパイ
test('注文確定時にメールが送信される', function () {
Mail::fake();
$order = Order::factory()->pending()->create();
$admin = User::factory()->admin()->create();
$this->actingAs($admin)
->patch("/admin/orders/{$order->id}/confirm");
Mail::assertQueued(OrderConfirmation::class, function ($mail) use ($order) {
return $mail->hasTo($order->customer_email);
});
});
test('外部APIが失敗した場合のエラーハンドリング', function () {
Http::fake([
'api.external.com/*' => Http::response(['error' => 'Service Unavailable'], 503),
]);
expect(fn() => app(ExternalApiService::class)->fetch())
->toThrow(\RuntimeException::class, 'API request failed');
});
テストの実行
# 全テスト実行
./vendor/bin/pest
# 並列実行(高速)
./vendor/bin/pest --parallel
# 特定のテストのみ
./vendor/bin/pest --filter="注文の管理"
# カバレッジ表示
./vendor/bin/pest --coverage --min=80
# Laravel Artisanから
php artisan test
まとめ
Pestを使うとPHPUnitより少ない記述量で読みやすいテストが書けます。describe・beforeEach・dataset を組み合わせることで、複雑なテストシナリオも整理された形で記述できます。弊社では新規プロジェクトからPestを採用しています。
LaravelによるWebシステム開発のご相談はお気軽にどうぞ。