[Laravel] PHPUnitテスト初心者ガイド

[Laravel] PHPUnitテスト初心者ガイド

  • Post Author:

はじめに

最近、業務でPHPUnitを使用してテストコードを書く機会が増えてきました。過去のテストコードを参考にしながら何とか作成できてはいるものの、自身の知識不足を感じる場面も多く、苦労しています。

そこで今回はPHPUnitの学習の一環として、学んだことを備忘録としてエンジニアブログにまとめようと思います。現在使用しているLaravelのバージョンの都合上、Laravel 10.xで使える知識を中心に調べましたが、本記事では最新版のLaravel 12.11.1でも利用可能な情報を記載しています。PHPUnitの基礎から、Laravel特有の機能(モデルファクトリ、HTTPテスト、モック、アサーション、E2Eテストなど)までカバーします。

PHPUnitとは何か?

PHPUnitとは、PHPで広く使われているテストフレームワークです。Laravelプロジェクトには標準で組み込まれており、コードの動作を自動で検証する仕組みを提供します。開発者はPHPUnitを用いてテストコード(テストケース)を書き、アプリケーションの関数やクラスが期待通りに動くかを確認できます。これにより、変更による不具合(リグレッション)を素早く検出でき、手作業での動作確認を減らして開発効率と信頼性を向上させることができます。また、Laravelではコマンド一つでテストファイルの雛形を作成できるため、テストコードの作成が容易です。

PHPUnitでできること(単体テスト・機能テスト)

Laravel では、PHPUnitで主に単体テスト(Unit Test)機能テスト(Feature Test)を作成できます。テスト用にtests/Unittests/Featureディレクトリが用意されており、それぞれに対応するテストを配置します。

  • 単体テスト: アプリケーションの「単体」動作を検証します。特定のクラスや関数にフォーカスし、そのメソッドが期待した結果を返すか、内部ロジックが正しく機能するかを確認します。単体テストでは外部のデータベースやフレームワーク部分に依存せず、できるだけ独立して小さな単位をテストするのが基本です。
  • 機能テスト: アプリケーションの機能全体を通じて検証します。Laravelでは実際に特定のルーティングにリクエストを送り、コントローラやミドルウェア、ビューを含めた一連の処理結果を確認できます。例えば、あるURLにGETリクエストした際にステータスコード200が返り、期待するビューやデータが得られるかをチェックします。フィーチャーテストではLaravelフレームワークが実際に起動するため、データベースや認証なども含めた統合的なテストが可能です。

Laravelではターミナルから Artisan コマンドを使ってテストファイルを作成できます。例えば、php artisan make:test SampleTestコマンドで機能テストの雛形(tests/Feature/SampleTest.php)が生成されます(--unitオプションを付けると単体テスト用の雛形を生成)。作成されたテストクラスに対してテスト内容(入力値や期待値)とアサーション(検証条件)を記述し、php artisan testまたは./vendor/bin/phpunitでテストを実行します。

Webアプリケーションのテストで確認すべき主な項目

Webアプリケーションをテストする際、以下のようなポイントを重点的に確認すると良いでしょう。

  • ルーティング: 定義したURLに正しくアクセスできるか、期待するコントローラや処理が呼ばれるかを確認します。存在しないURLへのアクセスが404を返すか、認可が必要なルートで未ログインユーザがリダイレクトされるかなど、ルーティング周りの挙動をテストします。
  • バリデーション: フォームやAPIの入力検証が正しく機能しているかを確認します。想定通りの不正な入力に対してエラーメッセージが返るか、有効な入力ではエラーなく処理が通るかをテストします。Laravelではフォームリクエストやvalidateメソッドで実装したバリデーションルールが、テストでも適切に動作することをチェックします。
  • レスポンス: コントローラの返すHTTPレスポンスが正しいかを確認します。具体的には、ステータスコード(200, 302, 404, 500など)が期待通りか、返されるビューやリダイレクト先、JSONデータの内容が仕様に沿っているかをテストします。例えば、ページ表示のテストではHTML中に特定のテキストが含まれるか(後述のassertSeeなどで検証)、APIのテストではJSONのキーや値が正しいか(後述のassertJson系で検証)を確認します。
  • データベース操作: アクションによってデータベースが正しく更新・保存されるかを確認します。フォーム送信でレコードが作成される、更新処理で正しい値に変わる、削除処理でデータが消える、といった期待通りのDB操作が行われたかをテストします。また、副作用として不要なレコードが残っていないか、関連するテーブルにも正しくデータが入っているかなども確認対象です。Laravelのアサーション(assertDatabaseHasなど)を使えば、特定のテーブルに指定した条件のデータが存在するかを簡単に検証できます(詳細は後述)。

上記の他にも、セッションやクッキーの内容、認証や権限の挙動(ログインが必要なページへのアクセス制御)、メール送信やイベント発行など、アプリケーション固有の重要な機能についても必要に応じてテストケースを追加します。

テスト時の一時データ作成とロールバック方法

テストを実行する際には、本番や開発環境のデータベースとは切り離されたテスト用データベースを使用することが推奨されます。Laravelでは.env.testingphpunit.xmlにテスト用のDB接続を設定することで、テスト実行時に別のデータベース(例:SQLiteのメモリデータベース)を利用できます。例えば、Laravelのデフォルトのphpunit.xmlには以下のようなSQLiteメモリデータベース設定がコメントアウトで含まれているので、有効化すればテスト毎にクリーンなメモリDBが利用できます:

<!-- <env name="DB_CONNECTION" value="sqlite"/> -->
<!-- <env name="DB_DATABASE" value=":memory:"/> -->

テスト中に作成された一時的なデータは、各テストが終わるごとにロールバック(元に戻す)して、テスト間でデータが干渉しないようにする必要があります。Laravelはこれを簡単にするために、Illuminate\Foundation\Testing\RefreshDatabaseというトレイトを提供しています。このトレイトをテストクラスでuseすると、各テストメソッドの実行前後で自動的にデータベースをリセットしてくれます。

  • RefreshDatabaseトレイト: データベースをリフレッシュし、前のテストで発生したDBへの変更が次のテストに影響しないようにします。具体的には、テスト用のデータベーススキーマが最新であれば各テストをトランザクション内で実行し(テスト終了時にロールバック)、スキーマに変更がある場合はマイグレーションを実行してクリーンな状態にしてからテストを行います。これにより、高速かつ安全にテストデータをリセットできます。
  • DatabaseTransactionsトレイト: 古い方法ですが、各テストをトランザクション内で実行し、終了時にROLLBACKすることでDBを元に戻すトレイトです。RefreshDatabaseと似ていますが、こちらは常にトランザクションを利用します。ただしトランザクション対応していない処理(他DBとの連携など)がある場合は注意が必要です。
  • DatabaseMigrations / DatabaseTruncationトレイト: テスト開始時にマイグレーションを実行し、終了時にロールバックまたはテーブルを空にする手法です。確実にデータを消去できますが、全テストで毎回マイグレーションを行うため実行速度は遅くなりがちです。現在はRefreshDatabaseで代替できるケースが多いでしょう。

通常はRefreshDatabaseトレイトの使用が簡単で高速なため便利です。例えばテストクラスで以下のように指定します。

use Illuminate\Foundation\Testing\RefreshDatabase;

class ExampleTest extends TestCase
{
    use RefreshDatabase;
    // ... 各テストメソッド ...
}

こうしておけば、各テストメソッドで追加・変更したレコードはメソッド終了時に自動でロールバックされ、次のテストはクリーンな状態から開始できます。

Laravelのモデルファクトリによるテストデータ作成

Laravelにはモデルファクトリ(Model Factory)と呼ばれる機能があり、テスト用のレコードを簡単に生成できます。モデルファクトリでは、各モデルに対してデフォルトのテストデータを定義しておくことで、テスト内で手軽にモデルインスタンスやDBレコードを作成できます。

例えば、Userモデルに対してファクトリを定義しておけば、以下のように一行でユーザーレコードをデータベースに作成できます。

use App\Models\User;

$user = User::factory()->create();  // テスト用のユーザーを1件DBに生成

上記を実行すると、ユーザーモデルのファクトリで定義された初期値(名前やメールアドレスなどFakerを使ったダミーデータ)が自動的に入力された状態で、usersテーブルにレコードが1件挿入されます。create()の代わりにmake()を使えばデータベースには保存されず、モデルインスタンスだけを生成することも可能です。

ファクトリは複数レコードの生成や関連モデルの生成にも対応しており、たとえばUser::factory()->count(5)->create()とすれば5人のユーザーを一度に作成できます。また、belongsTo/hasManyなどリレーションを定義している場合、それに対応する状態のファクトリ(例えば投稿付きのユーザー等)を用意しておくことで、複雑なデータ状態も簡潔に準備できます。

Laravel 8以降では、ファクトリは各モデル毎にPHPクラスとして定義されており(database/factoriesディレクトリにあります)、コード補完が効く形で扱えるようになっています。Laravel 7以前のファクトリ定義(クロージャベース)とは記法が異なるため、最新のLaravelでは公式ドキュメントに沿ってクラスベースのファクトリを作成・利用してください。

HTTPリクエストのテスト方法(GET・POST)

Laravelの機能テストでは、実際にアプリケーションに対してHTTPリクエストを送る形でテストを行います。PHPUnitのテストケース内で$this->get$this->postメソッドを使うと、指定したURLに対するGET/POSTリクエストをシミュレートできます。これらのメソッドは実際にサーバーを起動するわけではなく、フレームワーク内部でネットワーク通信をエミュレートしてレスポンスを返してくれます。

例えば、ルート/にGETリクエストしてレスポンスを取得し、ステータスコードを確認するには以下のように書きます。

$response = $this->get('/');        // GETリクエストをシミュレート
$response->assertStatus(200);      // ステータスコード200(OK)を期待

$this->get$this->postの戻り値はIlluminate\Testing\TestResponseオブジェクトです。TestResponseにはレスポンス内容を検査するための様々なアサーションメソッド(後述)が用意されており、例えば上記のassertStatusもその一つです。これにより、コントローラから返されたレスポンスの中身(ステータスコード、ヘッダ、ボディなど)を簡潔に検証できます。

POSTリクエストの場合は第二引数にフォームデータ(配列)を渡します。例えば、/loginにメールとパスワードをPOSTする場合:

$response = $this->post('/login', [
    'email' => '[email protected]',
    'password' => 'secret',
]);
$response->assertRedirect('/home');  // ログイン後 /home へリダイレクトすることを期待

上記では、ログイン用URLに認証情報を送り、処理結果として/homeへのリダイレクトが発生することをassertRedirectで検証しています。注意: Laravelのフォーム送信にはCSRFトークンが必要ですが、テスト時には自動でCSRF検証が無効になるためその点は気にせずリクエストできます。

他にも$this->put$this->deleteなどHTTP動詞に対応するメソッド、JSONリクエスト専用の$this->postJson$this->getJsonといったメソッドもあります。後者はJSONレスポンスを便利に検証できるassertJson系メソッドと組み合わせてAPIテストを記述するのに便利です。いずれの場合も、実際のHTTP通信は行われずフレームワーク内で処理が完結するため、外部ネットワークに依存しない安定したテストが可能です。

モックやスタブによるテストの効率化

大規模なテストや単体テストでは、テスト対象から一部の依存を切り離すためにモック(Mock)スタブ(Stub)を活用します。モックとは、本物のオブジェクトの代わりに振る舞いを模倣したダミーオブジェクトです。特定のメソッドが呼ばれたかを検証したり、返り値を強制したりする目的で使用します。一方、スタブは主に「呼び出せるダミーメソッド」を持ったオブジェクトで、テスト中に固定の結果を返すために使います(厳密な違いはありますが、一般的にはモックもスタブも「テストダブル」としてまとめて扱われます)。

Laravelのテストでは、PHPUnitに統合されたMockeryライブラリを使ってモックを作成する方法が一般的です。また、LaravelのTestCaseクラスにはモック作成を簡略化するヘルパーメソッドが用意されています。

  • Mockeryを直接使用: 例えば外部サービスを呼び出すクラスExternalServiceをモックする場合、$mock = \Mockery::mock(ExternalService::class)のように生成し、$mock->shouldReceive('fetchData')->andReturn('dummy')のように特定メソッドが呼ばれた際の挙動を定義できます。こうしたモックオブジェクトをテスト対象のクラスに差し込む(依存性注入する)ことで、外部サービスを実際には呼び出さずに挙動だけエミュレートできます。
  • Laravelの$this->mock()メソッド使用: LaravelのTestCaseでは、コンテナにバインドされたクラスに対して簡単にモックを作成・適用するためのmock()メソッドが利用できます。例えば、あるコントローラがApp\Services\PaymentServiceを利用している場合、テスト内で$this->mock(PaymentService::class, function (MockInterface $mock) { $mock->expects('process')->andReturn(true); });のように記述すれば、PaymentServiceクラスのprocessメソッドが呼ばれた際にtrueを返すモックが生成され、Laravelのサービスコンテナ内で差し替えられます。テスト中にコントローラを呼び出すと、このモックが自動的に使われるため、実際の外部処理を行うことなく結果だけをテストできます。

Laravelでは部分的にモック化するpartialMock()や、呼び出し履歴だけを記録するspy()といった機能も提供されています。たとえば「一部のメソッドだけモックし、他は実際の処理をそのまま実行する」といった柔軟なテストも可能です。

モックを活用することで、テストの独立性速度を向上できます。外部APIやメール送信、ファイルシステムアクセスなど副作用の大きい機能はモックに置き換えることで、ネットワークや環境に依存しない安定したテストが書けます。ただし、モックの設定自体が間違っているとテストが正しくアプリの不具合を検知できなくなるため、モックは必要な箇所に絞って使い、アプリケーションの主要なロジック部分はできるだけ実際の動作をテストする方が望ましいでしょう。

よく使うアサートメソッド

アサート(Assert)メソッドとは、テスト内で期待する結果を検証するための関数です。PHPUnitやLaravelは多数のアサートメソッドを提供しています。以下に代表的なものを紹介します。

  • assertTrue(condition) / assertFalse(condition) – 与えられた条件が真(または偽)であることを確認します。例えば、$this->assertTrue(is_numeric(123));のように使い、条件式の評価結果が期待通りか検証します。シンプルなブーリアンチェックに使います。
  • assertEquals(expected, actual) / assertNotEquals(…) – 第一引数の期待値と、第二引数の実際の値が等しいこと(または等しくないこと)を確認します。ゆるやかな比較(==)で行われ、型が異なっていても値が等しければパスします。例えば、$this->assertEquals(5, $result);と書けば、変数$resultが5であることを検証します。厳密な比較をしたい場合はassertSame(===による比較)も利用できます。
  • assertNull(value) – 値がnullであることを確認します(assertNotNullもあります)。データが存在しないことを確認する場合などに使います。
  • assertDatabaseHas(table, array $data) – 指定したデータベースのテーブルに、引数で与えたキー・バリューに一致するレコードが存在することをアサートします。Laravel特有のアサーションで、例えば$this->assertDatabaseHas('users', ['email' => '[email protected]']);のように使うと、usersテーブルにメールアドレス[email protected]を持つ行があるかを確認できます。
  • assertDatabaseMissing(table, array $data) – 上記の逆で、テーブルに特定の条件に一致するレコードが「存在しない」ことを確認します。削除処理の後などに利用します。
  • assertCount(n, array|Countable $value) – 配列やコレクションなどの要素数が期待通りの件数$n$であることを確認します。例えば、$this->assertCount(3, User::all());でユーザーが3件存在することを検証できます。
  • assertInstanceOf(ClassName::class, $object) – オブジェクトが特定のクラス(またはそのサブクラス)のインスタンスであることを確認します。型を検証したい場合に使います。

これら以外にも数多くのアサートメソッドがあります。PHPUnit自体が提供するメソッド(一部抜粋: assertEmpty, assertGreaterThan, assertThrowsなど)に加え、Laravelはデータベース関連やレスポンス内容検証用の独自アサートを用意しています。アサートを適切に使うことで、テストコードを読みやすく書けるだけでなく、不具合発生時に「何が期待と違ったのか」を明確に示すことができます。

レスポンスに対する専用アサーション

Laravelの機能テストで取得したレスポンスTestResponseオブジェクト)には、レスポンス内容を検証するための専用アサートメソッドが多数用意されています。代表的なものを挙げます。

  • assertStatus(code) / assertOk() – レスポンスのHTTPステータスコードが指定した値であることを確認します。assertStatus(200)はステータスコードが200かどうかを確認し、assertOk()はステータスコードが200であることを簡潔に確認するエイリアスメソッドです。他にもassertNotFound()(404か確認)、assertUnauthorized()(401か確認)など主要なステータス向けのメソッドが用意されています。
  • assertRedirect(url) – レスポンスが指定されたURLへリダイレクト(ステータスコード302など)していることを確認します。例えば$response->assertRedirect('/login')は、レスポンスが/loginにリダイレクトしている(おそらく認可が必要でリダイレクトされた)ことを検証します。assertRedirect系には、名前付きルート用のassertRedirectToRouteや、署名付きルート用のassertRedirectToSignedRouteなどもあります。
  • assertSee(string, $escaped = true) / assertSeeText() – レスポンスボディ(HTMLテキストなど)に指定した文字列が含まれていることを確認します。例えば、コントローラが返すビューに「ユーザー登録成功」というメッセージが表示されるはずであれば、$response->assertSee('ユーザー登録成功');のように記述します。assertSeeはデフォルトで検索文字列をHTMLエスケープしてから探します(第二引数falseでエスケープ無効化可能)。assertSeeTextはタグを無視して純テキストとして探すメソッドです。対応する否定形としてassertDontSee/assertDontSeeTextもあります。
  • assertJson(array|Closure) – JSON形式のレスポンス内容を検証します。例えば、$response->assertJson(['success' => true]);のように書くと、JSONレスポンス中にsuccess: trueというキー・値が含まれていることを確認します。配列を渡すと部分一致で検証し、ネストしたキーもドット区切りで指定可能です。また、クロージャを渡してAssertableJsonオブジェクトを操作する高度な検証もできます(Laravel 8で導入。例: $response->assertJson(fn (AssertableJson $json) => $json->has('data')->missing('error'));)。
  • assertJsonPath(path, value) – JSONレスポンスの特定のパスにある値が期待通りか確認します。例えば$response->assertJsonPath('data.user.name', 'Satou');のように使うと、{"data": {"user": {"name": "Satou"}}}というJSONでnameが”Satou”かを検証できます。
  • assertJsonStructure(array) – レスポンスJSONが特定の構造(キー)を持つかを確認します。$response->assertJsonStructure(['user' => ['id', 'name']]);のように、必要なキーが存在することをチェックできます。
  • assertSessionHas(key, value) – リダイレクト直後のセッションに特定のデータ(フラッシュメッセージなど)が含まれていることを確認します。フォーム送信後のエラーメッセージがセッションに入っているか($response->assertSessionHasErrors()など)、成功メッセージが入っているかなどを検証できます。

上記のように、LaravelのTestResponseにはWebアプリケーションの動作検証に便利なアサートが揃っています。これらを組み合わせることで、「ステータスコードは200で、特定の文字列を含むHTMLが返り、データベースにも期待通り保存されている」といった複合的な確認も数行で表現できます。例えば:

$response = $this->post('/register', $data);
$response->assertStatus(302)->assertRedirect('/home');
$this->assertDatabaseHas('users', ['email' => $data['email']]);

とすることで、新規登録処理の一連の結果をテストできます。レスポンス専用アサーションは非常に豊富なので、詳細は公式ドキュメントの「HTTP Tests」の項を参照してください。

PHPUnitテストコードのサンプル

ここまで紹介した内容を踏まえ、Laravelでのテストコードの一例を示します。以下はブログ記事投稿機能を想定した機能テストのサンプルです。このテストでは、記事投稿用のフォーム送信をシミュレートし、ルーティングバリデーションレスポンスDB保存といった挙動をまとめて検証しています。また、テスト用ユーザーの作成にモデルファクトリを使用し、RefreshDatabaseトレイトでテスト後のロールバックも自動化しています

<?php

namespace Tests\Feature;

use Tests\TestCase;
use App\Models\User;
use App\Models\Post;
use Illuminate\Foundation\Testing\RefreshDatabase;

class PostFeatureTest extends TestCase
{
    use RefreshDatabase;  // テストごとにDBをリフレッシュ

    public function test_記事投稿の一連の流れ()
    {
        // 1. 準備: テストユーザーの作成と認証
        $user = User::factory()->create();
        $this->actingAs($user);  // このユーザーでログイン状態にする

        // 2. データ準備: 投稿フォームの入力データを用意
        $formData = [
            'title'   => 'テスト記事',
            'content' => 'これはテスト投稿の本文です。',
        ];

        // 3. 記事投稿リクエストをシミュレート(POST)
        $response = $this->post('/posts', $formData);

        // 4. 結果検証:
        // 4a. ステータスコードとリダイレクト先の確認
        $response->assertStatus(302);
        $response->assertRedirect('/posts');  // 投稿後は一覧ページへリダイレクト

        // 4b. データベースに投稿が保存されたことを確認
        $this->assertDatabaseHas('posts', [
            'title'   => 'テスト記事',
            'content' => 'これはテスト投稿の本文です.',
        ]);

        // 4c. 投稿一覧ページを表示し、投稿内容が見えることを確認
        $responseAfter = $this->get('/posts');
        $responseAfter->assertOk();
        $responseAfter->assertSee('テスト記事');
    }
}

上記のテストでは、以下の点を確認しています。

  • 認証ユーザーで記事投稿を行った場合、HTTPレスポンスがリダイレクト(302)になり、指定のURLに転送されること。
  • 投稿フォームのデータが正しくデータベースに保存されていること(assertDatabaseHas)。
  • リダイレクト後の投稿一覧ページで、新しく投稿した記事のタイトルが画面に表示されていること(assertSee)。

このように一つのテストメソッド内で複数のアサートを行うことで、機能の一連の流れを総合的に検証できます。実際のプロジェクトでは、さらにバリデーションエラー時の挙動(タイトルが空の場合は投稿が保存されずエラーを返す、等)や、権限がないユーザーによる投稿禁止など、シナリオごとに別のテストメソッドを用意してカバーしていきます。

古いBrowserKitTestingと現在の主流手法

Laravel 5系~6系の頃には、BrowserKitを用いたテスト手法が主流でした。かつてLaravelのテストでは$this->visit('/ページ')でページにアクセスし、$this->see('文字列')でページ内の文字を確認するといった書き方がされていました。また、リンクをクリックする$this->click('リンクテキスト')やフォーム送信を行う$this->submitForm()など、ブラウザ操作を模倣するメソッドが用意されていました。これらはlaravel/browser-kit-testingパッケージによって提供されるもので、内部的にはSymfonyのDomCrawlerを使ってHTMLを解析しながらテストを進める方式です。

しかし、Laravel 7以降ではBrowserKitベースのテストはコアから取り除かれ、代わりによりモダンで強力な手法が採用されています。現在のLaravelでは機能テスト(前述の$this->get/$this->postなどを使うもの)と、後述するLaravel Duskによるブラウザテストが主流です。通常のページ遷移やフォーム送信結果の確認程度であれば、BrowserKit時代のvisit/seeに相当することは機能テスト+アサーションで実現できます。例えば、以前のBrowserKitテストで:

$this->visit('/posts')->see('投稿一覧');

と書いていたものは、現在では:

$response = $this->get('/posts');
$response->assertStatus(200);
$response->assertSee('投稿一覧');

のように書きます。同様に、$this->click('詳細')して詳細ページに遷移する代わりに、直接目的のURLへ$this->get('/posts/1')を実行し、その結果を検証する形になります。つまり一つ一つのユーザー操作に対応するHTTPリクエストを順番に発行し、そのレスポンスを検証するというスタイルです。BrowserKitとはアプローチが異なりますが、Laravel標準のテスト機能でほとんどのシナリオを網羅できます。

一方、JavaScriptを伴う動的な画面操作や、実際のブラウザでしか検証できない挙動(フロントエンドのイベント発火など)のテストは機能テストだけでは難しい場合があります。そのようなケースでは、Laravel Duskという公式パッケージを利用したE2Eテストが推奨されています。Laravel Duskは後述するように実際にブラウザを自動操作してテストする仕組みで、BrowserKitTestingが担っていた役割を大きく発展させたものです。Laravel 5.4でDuskが導入されて以降、BrowserKitTestingは公式には非推奨となり、引き続き古い書き方を使いたい場合のみlaravel/browser-kit-testingパッケージを開発環境にインストールして利用する形になりました。

まとめると、現在の主流は以下の通りです:

  • ページ遷移やフォーム送信結果を確認する一般的なテスト: LaravelのHTTPテスト機能(フィーチャーテスト)を使用($this->get/post+各種assert...)。
  • ブラウザ上での動きをそのままテストしたい場合(JSの挙動など含む): Laravel Duskを使用。
  • BrowserKitの古いメソッドをどうしても使いたい場合: laravel/browser-kit-testingをインストールして従来のAPIを利用可能(非推奨、原則Duskへの移行が望ましい)。

LaravelにおけるE2Eテスト: Laravel Duskの活用

E2Eテスト(End-to-Endテスト)とは、ユーザーが実際に操作するところからバックエンドまで含めたシナリオを通しで検証するテストです。Laravelでは公式にLaravel Duskというパッケージが提供されており、ブラウザ操作の自動化によるE2Eテストを簡単に導入できます。Duskを使うことで、まるで人間がブラウザを操作しているかのようにリンクをクリックしたりフォームに入力したりする処理をプログラムで記述し、その結果画面に期待する要素が表示されるかなどを検証できます。

Laravel Duskの主な特徴と使い方は次のとおりです。

  • インストール: Duskは公式パッケージですがLaravel本体には含まれないため、まずプロジェクトに追加します。Composerで開発用パッケージとしてインストールし(例:composer require --dev laravel/dusk)、続いてphp artisan dusk:installコマンドを実行します。このコマンドにより、Dusk用の基底クラスDuskTestCaseや必要な設定ファイル、tests/Browserディレクトリが作成されます。インストール後、.envに自動でAPP_URL=http://localhost(テスト時に使うURL)が追記されるので、必要に応じて調整してください。
  • テストの作成: Artisanコマンドphp artisan dusk:make テスト名でブラウザテストのクラスを作成します。例えばphp artisan dusk:make LoginTestとすると、tests/Browser/LoginTest.phpが生成されます。DuskのテストクラスはLaravelのTests\DuskTestCaseを継承し、ブラウザ操作用のBrowserクラスを使用できるようになります。生成直後のファイルには一つサンプルとしてtestExampleメソッドがあり、中で$browser->visit('/')などの基本操作が記載されています。
  • 基本的な操作: Duskでは$browserオブジェクトを介してブラウザを操作します。例えば、あるテストケースでは以下のように書けます。
public function testExample()
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/')
                ->assertSee('Laravel');
    });
}

上記では、実際にブラウザで/ページを開き(デフォルトではAPP_URLに基づくURLになります)、ページ内に”Laravel”という文字列が見えることを検証しています。複数のブラウザ操作をチェインでつなげられるのが特徴です。例えば、ログインテストの場合:

$browser->visit('/login')
        ->type('email', '[email protected]')   // フォーム入力
        ->type('password', 'secret')
        ->press('Login')                     // ボタン押下
        ->waitForLocation('/home')           // /homeに遷移するまで待機
        ->assertPathIs('/home');             // 現在URLが/homeであることを確認

このように、画面上の要素に対する操作メソッドが豊富に用意されています。typeは入力フィールドに文字を入力し、pressはボタンのクリック、clickLinkはリンクテキストをクリック、selectはプルダウン選択、check/uncheckはチェックボックス操作、attachはファイルアップロード、など多彩です。また、assertSeeassertDontSee, assertInputValue等で画面に現れたテキストやフォームの状態を検証できます。

  • テストの実行: Duskテストは通常のPHPUnitテストとは分離されており、php artisan duskコマンドで実行します。実行すると、tests/Browser配下の全てのブラウザテストが順次起動します。バックグラウンドでChromeDriver(ヘッドレスモードのChromeブラウザ)が立ち上がり、コードで記述したとおりにブラウザ操作が行われます。テスト完了後、ブラウザは自動で閉じられます。特定のテストクラスやメソッドだけ実行したい場合は、通常のPHPUnitと同様にコマンドの後にパスや--filterオプションで絞り込むことも可能です。
  • 環境依存について: Duskは内部的にChromeブラウザを操作するため、ローカルマシンにGoogle Chrome(もしくはChromium)が必要です。しかし、SeleniumサーバーやJava(JDK)のインストールは不要です。php artisan dusk:install時に適切なバイナリがセットアップされるため、そのまますぐ使えます。CI環境(例えばGitHub Actions)でもセットアップスクリプトでChromeをインストールすればDuskテストを実行可能です。なお、本番環境のような重要な環境ではDuskのService Providerが自動的に無効化される設計になっており、安全面にも配慮されています。

Laravel Duskを使えば、人がブラウザで行う操作をそのまま再現できるため、エンドユーザー視点での動作検証が可能です。例えば、「ユーザーがフォームに入力して送信し、成功メッセージが表示される」という一連の流れをE2Eテストとしてスクリプト化できます。これにより、バックエンドとフロントエンドの結合部分やJavaScriptの挙動も含めた包括的なテストが実施できます。

おわりに

LaravelにおけるPHPUnitテストの基礎から応用までを概観しました。まずは単体テストや機能テストでバックエンドの挙動を固め、必要に応じてDuskでフロントエンドも含めたE2Eテストを書くことで、信頼性の高いLaravelアプリケーションを構築していきましょう。テストコード自体もプロダクトの重要な一部です。小さくてもまず書いてみることで慣れていき、段階的に網羅範囲を広げていくと良いでしょう。あなたのLaravel開発にテストを書いて安心感をプラスできれば幸いです。

参考資料:Laravel公式ドキュメント(Testing, HTTP Tests, Database Testing, Dusk)、Laravel関連ブログ記事など

we are hiring

優秀な技術者と一緒に、好きな場所で働きませんか

株式会社もばらぶでは、優秀で意欲に溢れる方を常に求めています。働く場所は自由、働く時間も柔軟に選択可能です。

現在、以下の職種を募集中です。ご興味のある方は、リンク先をご参照下さい。

コメントを残す