こんにちは、かつコーチです。
前回はPHPUnitの基本と、テストの書き方の土台を解説しました。
今回は、Laravel独自の仕組みであるFeature Testを使って、コントローラやルーティングを含めた「機能全体」をテストする方法を解説します。
Feature Testとは
Unit TestとFeature Testの違い
前回少し触れた通り、Laravelのテストは大きくUnit TestとFeature Testに分かれます。
- Unit Test:クラスやメソッド単体のロジックを、他の要素から切り離してテストする
- Feature Test:実際にHTTPリクエストを送り、ルーティング→コントローラ→レスポンスという一連の流れをテストする
Feature Testは「ユーザーが実際にブラウザで操作したときの動き」に近い形でテストできるのが特徴です。
どんな場面でFeature Testを使うのか
たとえば次のような検証はFeature Testの得意分野です。
- ログインしていないユーザーが管理画面にアクセスすると、ログイン画面にリダイレクトされる
- フォームを送信すると、データベースに正しく保存される
- 存在しないIDにアクセスすると、404エラーが返る
「画面を開いて、実際に動かして確認したいこと」は、たいていFeature Testで再現できます。
Feature Testを書いてみる
テストファイルの作成
Feature Testは--unitオプションを付けずに作成します。
php artisan make:test PostControllerTest
tests/Feature/PostControllerTest.phpが作成されます。
GETリクエストのテスト
まずは、一覧画面が正常に表示されるかをテストしてみます。
<?php
// tests/Feature/PostControllerTest.php
namespace Tests\Feature;
use Tests\TestCase;
class PostControllerTest extends TestCase
{
public function test_投稿一覧ページが表示できる(): void
{
$response = $this->get('/posts');
$response->assertStatus(200);
}
}
$this->get('/posts')で実際にGETリクエストを送り、assertStatus(200)でレスポンスのステータスコードが200(正常)であることを検証しています。
POSTリクエストとデータベースの検証
フォーム送信のテストでは、送信後にデータベースへ正しく保存されたかも合わせて確認します。
<?php
public function test_投稿を新規作成できる(): void
{
$response = $this->post('/posts', [
'title' => 'テスト記事',
'body' => 'これはテスト用の本文です',
]);
$response->assertRedirect('/posts');
$this->assertDatabaseHas('posts', [
'title' => 'テスト記事',
]);
}
assertRedirectでリダイレクト先を、assertDatabaseHasで該当レコードがテーブルに存在するかを検証しています。
認証状態を伴うテスト
ログイン済みユーザーとしてリクエストする
管理画面のように、ログインが必要な画面をテストする場合はactingAsを使います。
<?php
use App\Models\User;
public function test_ログイン済みユーザーは管理画面を開ける(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)->get('/admin/dashboard');
$response->assertStatus(200);
}
actingAs($user)を挟むことで、そのユーザーとしてログインした状態のリクエストを再現できます。
未ログイン状態のテストも忘れずに
「ログインしていれば見られる」だけでなく、「ログインしていなければ見られない」ことも合わせて検証しておくと安心です。
<?php
public function test_未ログインユーザーは管理画面にアクセスできずリダイレクトされる(): void
{
$response = $this->get('/admin/dashboard');
$response->assertRedirect('/login');
}
私は最初のうち「見られるケース」ばかりテストしていて、「見られてはいけないケース」の確認が漏れていました。
権限まわりのバグは「見えてはいけないものが見えてしまう」形で表面化しやすいので、Feature Testでは両方のパターンを意識的に書くようにしています。
よく使うアサーション
レスポンス内容を検証するアサーション
Feature Testでは、レスポンスの内容を検証する専用メソッドが充実しています。
| メソッド | 用途 |
|---|---|
assertStatus($code) | ステータスコードを検証 |
assertRedirect($url) | リダイレクト先を検証 |
assertSee($text) | レスポンス内に特定の文字列が含まれるか検証 |
assertDatabaseHas($table, $data) | テーブルに該当レコードが存在するか検証 |
assertDatabaseMissing($table, $data) | テーブルに該当レコードが存在しないか検証 |
バリデーションエラーの検証
フォームの必須項目チェックなど、バリデーションが正しく効いているかもFeature Testの重要な確認ポイントです。
<?php
public function test_タイトル未入力だとバリデーションエラーになる(): void
{
$response = $this->post('/posts', [
'title' => '',
'body' => '本文だけ入力',
]);
$response->assertSessionHasErrors('title');
}
assertSessionHasErrorsで、指定した項目にバリデーションエラーが発生しているかを検証できます。
つまずきやすいポイント:本番用DBに向けてテストしてしまう
データが消えてしまった実体験
Feature Testを書き始めたばかりの頃、テスト用のデータベース設定をせずにテストを実行してしまい、開発用DBのデータがテストの後片付け処理で消えてしまったことがあります。
Laravelのテストは、デフォルトの設定のままだと.envに書かれたデータベースに接続してしまうため、注意が必要です。
❌ Before:テスト用DBを分けずに実行してしまう
<?php
// phpunit.xml でDB設定を上書きしていない状態
// .envのDATABASE設定がそのまま使われてしまい、開発用DBに書き込まれる
✅ After:phpunit.xmlでテスト専用のDBを指定する
<!-- phpunit.xml -->
<php>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
</php>
DB_CONNECTIONをsqlite、DB_DATABASEを:memory:にしておくと、テスト実行時だけメモリ上に一時的なデータベースが作られ、開発用DBに影響を与えずに済みます。
この設定に加えて、次回解説するRefreshDatabaseトレイトを併用することで、テストごとにデータベースをまっさらな状態から始められるようになります。
まとめ
この記事のポイント
- Feature Testは、ルーティングからレスポンスまでの一連の流れを検証するテスト
get・postでリクエストを再現し、assertStatusやassertDatabaseHasで結果を検証するactingAsでログイン済み状態を再現し、ログイン有無それぞれのパターンをテストする- テスト実行時は
phpunit.xmlでテスト専用のデータベースを指定し、開発用DBを壊さないようにする
次に読むべき記事
次回は、コントローラではなくモデルやサービスクラス単体をテストする「Unit Test」を解説します。
→ 次の記事:Unit Testでモデル・サービスクラスをテストする