Laravelテスト完全ガイド2026:Pest 4・モック・アーキテクチャテスト・技術面接対策

LaravelにおけるPest 4を用いたテスト手法を徹底解説。ユニットテスト、フィーチャーテスト、ファサードのモック、アーキテクチャテスト、ミューテーションテスト、技術面接の頻出質問までを網羅的に紹介します。

LaravelテストとPestによるモック・アーキテクチャテスト・面接対策2026ガイド

PHPにおけるソフトウェア品質は、アプリケーションコードの設計と同様に、テストスイートの堅牢性に大きく依存しています。Laravelのエコシステムでは、Pestがテストフレームワークのデファクトスタンダードとして定着し、多くのプロダクション環境のプロジェクトでPHPUnitの直接利用に取って代わりました。2026年現在、Pest 4、ファサードのモック、アーキテクチャテストを使いこなせることは、PHP開発者の採用プロセスにおいて不可欠な技術要件となっています。Laravelをプロダクション環境にデプロイするチームは、表現力豊かなテストの記述、意味のあるカバレッジの維持、そしてプロフェッショナルなテストスイートの構築を候補者に求めています。

本記事では、Pestを用いたLaravelテストの実践手法を、初期設定からミューテーションテストまで包括的に解説します。各セクションはLaravel技術面接で直接評価される能力に対応しており、実技試験や実務プロジェクトにそのまま転用できるコード例を掲載しています。

なぜPHPUnit単体ではなくPestを使うのか

PestはPHPUnit上に構築されていますが、JestやRSpecに着想を得た表現力の高い構文を提供します。expect()->toBe() チェーンは従来の assertEquals() を置き換え、describe/it ブロックによってテストが読みやすく構造化されます。さらに、アーキテクチャテストやミューテーションテストがネイティブ機能として組み込まれているため、サードパーティパッケージへの依存が軽減されます。PestはPHPUnitと完全な互換性を維持しており、既存のPHPUnitテストは一切の変更なくPestスイートで実行可能です。

Laravel 12におけるPest 4のセットアップ

LaravelプロジェクトでのPestの設定は、tests/Pest.php ファイルを中心に行います。このファイルでは、各テストファイルに自動的に適用されるトレイトとベースクラスを定義します。設定の一元化により、共通宣言の繰り返しを排除し、テストスイート全体でのテスト環境の一貫性を保証します。

tests/Pest.phpphp
use Illuminate\Foundation\Testing\RefreshDatabase;

pest()
    ->extend(Tests\TestCase::class)
    ->use(RefreshDatabase::class)
    ->in('Feature');

extend() メソッドは、LaravelのTestCaseクラスをすべてのテストに関連付けます。これにより、HTTPメソッド(getJsonpostJson)、認証シミュレーション(actingAs)、レスポンスアサーションへのアクセスが提供されます。RefreshDatabase トレイトは、トランザクションを利用してテスト間でデータベースをリセットし、マイグレーションの再実行コストなしに完全な分離を実現します。

->in('Feature') ディレクティブは、この設定をフィーチャーテストにのみ適用するよう制限しています。tests/Unit/ に配置されたユニットテストは、データベース接続を持たないより軽量な環境を継承するため、実行速度が向上します。ユニットテストとフィーチャーテストのこのアーキテクチャ上の分離は、古典的なテストピラミッドを反映しており、技術面接においても頻繁に評価されるポイントです。

ユニットテストとフィーチャーテストの使い分け

Laravelにおけるフィーチャーテストは、受信HTTPリクエストからJSONレスポンスまたはリダイレクトまで、エンドポイントの完全な振る舞いを検証します。Pestのフルーエント構文を活用することで、ステータスアサーション、JSON構造の検証、データベース確認をシンプルにチェーン記述できます。

以下のテストは、REST APIを通じたユーザー登録を検証する例です。

tests/Feature/UserRegistrationTest.phpphp
use App\Models\User;

it('registers a new user with valid data', function () {
    $response = $this->postJson('/api/register', [
        'name' => 'Jane Doe',
        'email' => 'jane@example.com',
        'password' => 'SecurePass123!',
        'password_confirmation' => 'SecurePass123!',
    ]);

    $response->assertStatus(201)
        ->assertJsonStructure(['user' => ['id', 'name', 'email']]);

    expect(User::where('email', 'jane@example.com')->exists())->toBeTrue();
});

いくつかの要素に注目する必要があります。postJson() メソッドは、JSONヘッダーを自動設定したPOSTリクエストを送信し、実際のAPIクライアントをシミュレートします。assertJsonStructure アサーションは、特定の値を強制することなく期待されるキーの存在を検証するため、レスポンスの軽微な変更に対してテストが耐性を持ちます。expect() による最後のアサーションは、ユーザーがデータベースに正しく永続化されたことを確認し、処理チェーン全体を検証します。

このHTTPレベルのエンドツーエンドテストアプローチは、Laravelにおける標準的な手法です。ルーティング、ミドルウェア、データバリデーション、コントローラーロジック、永続化を同時に検証するため、いずれの層での障害もテストに反映され、リファクタリング時の安全網として機能します。

ユニットテストは、データベース、ファイルシステム、外部サービスとの連携なしに、個々のクラスやメソッドを対象とします。ミリ秒単位で実行され、テストピラミッドの最も信頼性の高い基盤を構成します。Pestの describe/it ブロックにより、これらのテストが可読性の高い形で構造化されます。

tests/Unit/PriceCalculatorTest.phpphp
use App\Services\PriceCalculator;

describe('PriceCalculator', function () {
    it('applies a percentage discount correctly', function () {
        $calculator = new PriceCalculator();
        $result = $calculator->applyDiscount(150.00, 20);
        expect($result)->toBe(120.00);
    });

    it('rejects negative discount values', function () {
        $calculator = new PriceCalculator();
        expect(fn () => $calculator->applyDiscount(100.00, -5))
            ->toThrow(InvalidArgumentException::class);
    });
});

describe ブロックは同一クラスまたは機能に関連するテストをグループ化し、実行レポートの可読性を向上させます。toThrow() アサーションは、無効な入力に対して例外が正しくスローされることを検証する防御的パターンであり、ビジネスロジックにおいて不可欠な要素です。expect() にクロージャを渡すことで、テストを中断させることなく例外をキャプチャできます。

技術面接では、ユニットテストの対象(計算ロジック、データ変換、ビジネスルールのバリデーション)とフィーチャーテストの対象(HTTPインタラクション、データベース連携)を正確に区別する能力が、技術的成熟度の重要な指標として評価されます。

ファサードとMockeryによるモック

Laravelはファサードシステムを通じてフレームワークのサービスへのアクセスを簡素化しています。テストにおいては、これらのファサードが提供する fake() メソッドにより、実際の実装をテストダブルに置き換え、副作用を生じさせることなく受信した呼び出しを記録できます。このメカニズムは、メール送信、ジョブのディスパッチ、通知のトリガーを伴うプロセスのテストに不可欠です。

tests/Feature/OrderProcessingTest.phpphp
use App\Models\Order;
use App\Models\User;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Queue;
use App\Mail\OrderConfirmation;
use App\Jobs\ProcessPayment;

it('dispatches payment job and sends confirmation email', function () {
    Mail::fake();
    Queue::fake();

    $user = User::factory()->create();
    $order = Order::factory()->for($user)->create([
        'total' => 99.99,
        'status' => 'pending',
    ]);

    $this->actingAs($user)
        ->postJson("/api/orders/{$order->id}/confirm")
        ->assertOk();

    Queue::assertPushed(ProcessPayment::class, function ($job) use ($order) {
        return $job->order->id === $order->id
            && $job->order->total === 99.99;
    });

    Mail::assertSent(OrderConfirmation::class, function ($mail) use ($user) {
        return $mail->hasTo($user->email);
    });
});

Mail::fake()Queue::fake() の呼び出しは、テストの実行期間中、それぞれメール送信とジョブのディスパッチをインターセプトします。実際のメールは送信されず、バックグラウンドでジョブが実行されることもありません。assertPushedassertSent アサーションにより、適切なクラスが期待されたパラメータでディスパッチされたことを事後検証します。

Queue::assertPushed に渡すクロージャは、高度なパターンを示しています。ジョブがディスパッチされたことだけでなく、そのプロパティに正しい値が含まれていることまで検証できます。このアサーションの粒度により、リファクタリング時によく発生する、誤ったオブジェクトがジョブに渡されるという微妙なバグを検出できます。

モックはシステムの境界で行う

過度なモックはテストを脆弱にし、実装の詳細に密結合させてしまいます。ベストプラクティスは、外部APIコール、決済ゲートウェイ、メールサービス、サードパーティ連携といったシステムの境界でのみモックを行うことです。内部のビジネスロジックは、すべての依存関係をモックするのではなく、実際の入力と出力を通じてテストすべきです。

Laravelファサードではないサービスに対しては、Mockeryがテストダブルの振る舞いをきめ細かく制御する手段を提供します。Stripe、Twilio、社内APIなどのサードパーティサービスクライアントで頻繁に使用されるケースです。

tests/Feature/PaymentGatewayTest.phpphp
use App\Services\PaymentGateway;
use App\Services\StripeClient;

it('charges the customer through the payment gateway', function () {
    $stripeClient = Mockery::mock(StripeClient::class);
    $stripeClient->shouldReceive('charge')
        ->once()
        ->with('cus_abc123', 5000, 'usd')
        ->andReturn(['status' => 'succeeded', 'id' => 'ch_xyz']);

    $this->app->instance(StripeClient::class, $stripeClient);

    $gateway = app(PaymentGateway::class);
    $result = $gateway->processCharge('cus_abc123', 50.00);

    expect($result['status'])->toBe('succeeded');
});

Mockery::mock() メソッドは、メソッド呼び出しをインターセプトするテストダブルを生成します。shouldReceive()->once()->with()->andReturn() のチェーンは厳密な契約を定義しています。charge メソッドは指定されたパラメータで正確に1回呼び出されなければならず、定義された値を返します。この契約からの逸脱はテストの失敗を引き起こします。

$this->app->instance() は、Laravelのサービスコンテナにモックを登録します。PaymentGateway が依存性注入を通じて StripeClient のインスタンスを要求すると、コンテナは実際の実装の代わりにモックを提供します。このメカニズムは依存性逆転の原則に基づいており、適切に設計されたLaravelアプリケーションにおけるテスタビリティの要です。

processCharge に渡される金額(ドル単位の50.00)と charge に渡される金額(セント単位の5000)の違いは、PaymentGateway 内の変換ロジックの存在を示しています。このテストは暗黙的にこの変換を検証し、クラスのコードを直接検査することなく計算のリグレッションを検出します。

Laravelの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

Pestプリセットによるアーキテクチャテスト

PestはPHPエコシステムにおいて独自の機能であるアーキテクチャテストを導入しました。これらのテストは、アプリケーションロジックを実行することなく、ソースコードがチームの定義した構造的な規約に準拠しているかを検証します。違反は静的に検出され、高度なリンターのように機能します。

tests/Architecture/ArchitectureTest.phpphp
arch('controllers do not use Eloquent directly')
    ->expect('App\Http\Controllers')
    ->not->toUse('Illuminate\Database\Eloquent');

arch('services are final classes')
    ->expect('App\Services')
    ->toBeFinal();

arch('no debugging functions in production code')
    ->expect(['dd', 'dump', 'var_dump', 'ray'])
    ->not->toBeUsed();

最初のテストは厳密なアーキテクチャ上の分離を強制しています。コントローラーがEloquentを直接操作することを禁止し、サービス層またはリポジトリの使用を強制します。この制約は、Laravelアプリケーションにおける古典的なアンチパターンであるコントローラーへのビジネスロジックの蓄積を防止します。

2番目のテストは、サービスクラスが final として宣言されていることを保証し、メンテナンスを複雑にする無秩序な継承を防ぎます。3番目のテストは、プロダクションコードに残されたデバッグ関数を検出します。これは本番環境での情報漏洩を引き起こす問題です。

Pestは、Laravelの標準的な規約をカバーする事前構成済みのアーキテクチャプリセットも提供しています。

tests/Architecture/LaravelPresetTest.phpphp
arch()->preset()->laravel();
arch()->preset()->security();
arch()->preset()->php();

laravel() プリセットは、コントローラーが正しいベースクラスを継承しているか、モデルが適切なネームスペースに配置されているか、命名規約が遵守されているかを検証します。security() プリセットは、eval()exec()shell_exec() などの危険な関数の使用を検出します。php() プリセットは、PHP言語の一般的なベストプラクティスを強制します。

技術面接において、アーキテクチャテストの知識は、ソフトウェア品質に対する包括的なビジョンを持つ候補者と、フィーチャーテストのみに依存する候補者を区別する判断材料となります。これらのテストは、チームのアーキテクチャ上の意思決定をコード化し、CIパイプラインで自動実行可能にします。

ミューテーションテストによるテスト品質の評価

コードカバレッジは、テスト実行中にどの行が実行されたかを測定しますが、アサーションの品質については何も示しません。ミューテーションテストは、ソースコードを体系的に改変(演算子の置換、条件の削除、戻り値の変更)し、テストが各ミューテーションを検出できるかを検証することで、このギャップを埋めます。ミューテーションにもかかわらずパスするテストは、十分に精密でないことを意味します。

Pestはバージョン3以降、ミューテーションテストをネイティブに統合しており、追加の設定なしにこの手法を利用できます。

bash
php artisan test --mutate --class=App\\Services\\PriceCalculator

このコマンドは対象クラスを分析し、ミュータントを生成し、それぞれに対してテストスイートを実行します。レポートには、既存のテストによって検出されたミュータントの割合であるミューテーションスコアが表示されます。

以下の例は、高いカバレッジだがミューテーションスコアが低いテストと、真に堅牢なテストの違いを示しています。

php
it('calculates shipping cost', function () {
    $cost = calculateShipping(weight: 5.0, zone: 'domestic');
    expect($cost)->toBeFloat();
});

it('calculates domestic shipping for 5kg package', function () {
    $cost = calculateShipping(weight: 5.0, zone: 'domestic');
    expect($cost)->toBe(12.50);
});

最初のテストは、計算式が完全に変更されてもパスしてしまいます。任意の浮動小数点数が toBeFloat() アサーションを満たすためです。2番目のテストは期待される正確な値を要求し、計算ロジックのあらゆるミューテーションを検出します。この広範なアサーションと精密なアサーションの違いは、面接官が定期的に評価する基本概念です。

ミューテーションテストは、定量的なカバレッジが質的な弱点を覆い隠しているコード領域を明らかにします。ミューテーションスコアが90%以上であれば、100%の行カバレッジでは見逃してしまう微妙なリグレッションを検出できる、真に信頼性の高いテストスイートであることを示します。

ファクトリーを活用したデータベーステスト

Laravelのファクトリーは、一貫したデフォルト値を持つテストデータの作成を簡素化します。テスト対象に関連する属性のみを指定すれば済むため、テストの意図が明確になります。for()has() メソッドによりモデル間のリレーションが自動管理され、データ作成の繰り返しコードが排除されます。

以下のテストは、記事公開の完全なワークフローを検証する例です。

tests/Feature/ArticlePublishingTest.phpphp
use App\Models\Article;
use App\Models\User;

it('publishes a draft article and updates the timestamp', function () {
    $author = User::factory()->create(['role' => 'editor']);

    $article = Article::factory()
        ->for($author, 'author')
        ->draft()
        ->create(['title' => 'Testing Best Practices']);

    $this->actingAs($author)
        ->patchJson("/api/articles/{$article->id}/publish")
        ->assertOk()
        ->assertJsonPath('data.status', 'published');

    $article->refresh();

    expect($article->status)->toBe('published')
        ->and($article->published_at)->not->toBeNull()
        ->and($article->published_at->isToday())->toBeTrue();
});

draft() ファクトリーステートは下書き状態の記事を作成し、初期コンテキストをシミュレートします。assertJsonPath アサーションは、レスポンスJSON全体の構造を強制することなく、特定のパスの値を検証します。refresh() の呼び出しはデータベースからモデルを再読み込みし、アサーションがローカルキャッシュではなく永続化された状態に対して実行されることを保証します。

Pestの ->and() を使ったアサーションチェーンにより、単一のフルーエント式で複数のプロパティを検証でき、テストの可読性が向上します。isToday() の検証は、Laravelに統合されたCarbonの機能を活用して、公開タイムスタンプが当日であることを確認します。

パラレルテストによるスイートの高速化

Pestは --parallel フラグによるパラレルテストをサポートしており、複数のプロセスでテストを同時に実行できます。数百のテストを持つ大規模プロジェクトでは、この機能により実行時間を大幅に短縮できます。パラレルに実行されるテストは個別のデータベースを使用するため、各テストが完全に分離されており、共有状態に依存しないことを確認する必要があります。

HTTPレスポンスのテスト

HTTPテストは、認証を要するAPIの3つの基本的なシナリオをカバーします。未認証リクエストの拒否、認証済みユーザーへの正しいレスポンス、そして必須フィールドのバリデーションです。

tests/Feature/ApiAuthenticationTest.phpphp
use App\Models\User;

describe('API Authentication', function () {
    it('rejects unauthenticated requests with 401', function () {
        $this->getJson('/api/profile')
            ->assertUnauthorized();
    });

    it('returns the authenticated user profile', function () {
        $user = User::factory()->create([
            'name' => 'John Doe',
            'email' => 'john@example.com',
        ]);

        $this->actingAs($user)
            ->getJson('/api/profile')
            ->assertOk()
            ->assertJson([
                'data' => [
                    'name' => 'John Doe',
                    'email' => 'john@example.com',
                ],
            ]);
    });

    it('validates required fields on registration', function () {
        $this->postJson('/api/register', [])
            ->assertUnprocessable()
            ->assertJsonValidationErrors(['name', 'email', 'password']);
    });
});

assertUnauthorized() アサーションはステータス401を検証し、assertUnprocessable() はバリデーションエラーの標準ステータスである422に対応します。assertJsonValidationErrors の使用により、バリデーションエラーが正しいフォーマットで関連するフィールドに紐付けられていることを確認できます。このパターンは、各フォームフィールドの下にエラーメッセージを表示するフロントエンドクライアントが消費するAPIにとって必須です。

describe ブロックは認証関連のシナリオを単一の論理構造にグループ化し、テストスイートのナビゲーションとメンテナンスを容易にします。actingAs() メソッドは、実際のログインプロセスを経ることなく認証済みユーザーセッションをシミュレートし、テストを認証メカニズムから分離して、テスト対象エンドポイントの振る舞いにアサーションを集中させます。

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

技術面接でよく問われるLaravelテストの質問

Laravelの技術面接におけるテスト関連の質問は、構文の暗記ではなく、基盤となるメカニズムの理解度を評価します。以下のポイントは、最も頻繁に取り上げられるトピックを網羅しています。

fake()、mock()、spy() の違いは何ですか?

fake() はLaravelファサード専用のメソッドであり、Mail::fake()Queue::fake() のように、実装全体をテストダブルに置き換えます。Mockery::mock() は任意のクラスに対して厳密な期待値(呼び出し回数、パラメータ)を定義するテストダブルを生成します。Mockery::spy() はモックと類似していますが、事前の期待値定義なしにすべてのメソッド呼び出しを記録し、事後的にアサーションを行えます。面接では、ファサードにはfakeを使い、サードパーティサービスにはmockまたはspyを使うという使い分けの基準を明確に説明することが求められます。

RefreshDatabase と DatabaseTransactions の違いは何ですか?

RefreshDatabase トレイトはデータベーストランザクションを使用して各テスト後の変更をロールバックします。最初のテスト実行時にマイグレーションを実行し、以降はトランザクションの巻き戻しで高速にリセットします。DatabaseTransactions も同様にトランザクションを使用しますが、マイグレーションの自動実行は行いません。複数のデータベース接続を使用するテストでは、トランザクションベースのアプローチが制約となる場合があり、その際は DatabaseMigrations が必要になりますが、実行速度は大幅に低下します。

フィーチャーテストとユニットテストの使い分けの基準を説明してください。

ユニットテストは、外部依存なしにテスト可能な純粋なビジネスロジック(計算、データ変換、バリデーションルール)に適しています。フィーチャーテストは、HTTPリクエストの処理、ミドルウェア、データベースとの連携など、複数の層を横断する振る舞いの検証に適しています。テストピラミッドの原則として、ユニットテストを多く、フィーチャーテストを適度に、E2Eテストを少量にするのが理想的です。ただし、Laravelの場合はフレームワークとの統合が深いため、フィーチャーテストの比率がやや高くなる傾向があります。

ミューテーションテストがコードカバレッジを超える理由を説明してください。

コードカバレッジはテスト実行中にどの行が「通過」したかを測定するだけであり、アサーションの品質は評価しません。例えば、expect($result)->toBeFloat() というアサーションは対象の行をカバーしますが、計算結果の正確性は検証していません。ミューテーションテストはソースコードに意図的な変更(演算子の置換、条件の反転など)を加え、テストがこれらの変更を検出できるかを確認します。ミューテーションスコアが高いテストスイートは、コードの振る舞いを精密に検証しており、リファクタリング時のリグレッション検出において、行カバレッジよりもはるかに信頼性の高い指標となります。

アーキテクチャテストが通常のテストで検出できない問題とは何ですか?

アーキテクチャテストは、コードの構造と依存関係を実行なしに検証します。コントローラーがEloquentを直接使用している、デバッグ関数が残っている、サービスクラスがfinal宣言されていないといった規約違反を検出しますが、これらはフィーチャーテストでは検出できません。コードはアーキテクチャ上の違反があっても正しく動作するためです。

まとめ

2026年におけるPestを用いたLaravelテストは、単なる追加的な技術スキルを超え、PHP開発者としてのプロフェッショナルな信頼性の柱となっています。表現力豊かなテストを記述し、外部依存を適切にモックし、アーキテクチャ上の規約を自動的に維持する能力が、シニアレベルの開発者と中級開発者を区別する基準です。

Laravel面接対策に向けて押さえるべきポイントは以下の通りです。

  • Pestの設定: tests/Pest.php ファイルでトレイトとベースクラスを一元管理し、ユニットテストとフィーチャーテストを明確に分離する
  • フィーチャーテスト: postJson / assertStatus / expect の組み合わせでエンドポイントの処理チェーン全体をカバーする
  • ファサードのモック: Mail::fake()Queue::fake() と関連アサーションが非同期処理テストの標準パターンである
  • Mockery: サードパーティサービスに対して、サービスコンテナ経由でモックを注入し、依存性逆転の原則を遵守する
  • アーキテクチャテスト: チームの構造的な意思決定をコード化し、CI/CDパイプラインで自動実行可能にする
  • ミューテーションテスト: ミューテーションスコアが行カバレッジを超えた、アサーションの真の品質を明らかにする
  • ファクトリーとアサーション: Laravelファクトリーと粒度の細かいJSONアサーションの習熟が、保守しやすいテストの記述速度を向上させる

Laravelの採用プロセスにおいて、テストへの体系的なアプローチを実演することは、技術的な演習を専門的な能力のショーケースに変えます。適切に記述された各テストは、フレームワークへの深い理解と、将来のコードの信頼性についてチームを安心させる厳密さを示すものです。Laravel面接質問集も併せて確認し、より幅広い準備を進めてください。

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

タグ

#laravel
#testing
#pest
#php
#mocking
#best-practices
#interview

共有

関連記事