【Laravel】Feature Testでコントローラの動作をテストする

laravelアイキャッチ Laravel

こんにちは、かつコーチです。

前回は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_CONNECTIONsqliteDB_DATABASE:memory:にしておくと、テスト実行時だけメモリ上に一時的なデータベースが作られ、開発用DBに影響を与えずに済みます。

この設定に加えて、次回解説するRefreshDatabaseトレイトを併用することで、テストごとにデータベースをまっさらな状態から始められるようになります。

まとめ

この記事のポイント

  • Feature Testは、ルーティングからレスポンスまでの一連の流れを検証するテスト
  • getpostでリクエストを再現し、assertStatusassertDatabaseHasで結果を検証する
  • actingAsでログイン済み状態を再現し、ログイン有無それぞれのパターンをテストする
  • テスト実行時はphpunit.xmlでテスト専用のデータベースを指定し、開発用DBを壊さないようにする

次に読むべき記事

次回は、コントローラではなくモデルやサービスクラス単体をテストする「Unit Test」を解説します。

→ 次の記事:Unit Testでモデル・サービスクラスをテストする

タイトルとURLをコピーしました