【Laravel】テストの書き方入門|Feature Test・Unit Test・FactoryとFakerの使い方

動いている画面を少し直しただけのつもりが、ログイン後の導線や保存処理を壊してしまうことがあります。手元でブラウザを開いて確認しても、毎回同じ手順を漏れなく試すのは難しく、修正が増えるほど不安が残りがちです。

Laravel のテストは、この不安を小さくするための仕組みです。この記事では、Feature Test と Unit Test の違いを整理します。Factory と Faker によるデータ作成、HTTP レスポンスやデータベース状態の確認も、最小のコード例で扱います。

細かな API や標準のテストランナーは Laravel のメジャーバージョンやプロジェクト作成時の選択で変わることがあります。手元の composer.jsonlaravel/framework に合わせ、Laravel 公式ドキュメントの Testing 章と関連ページの該当バージョンを確認してください。

はじめに

この記事では、Laravel でテストを書き始める場面を想定し、次の範囲を扱います。

  • Feature Test と Unit Test の使い分け
  • HTTP リクエストを使った Feature Test
  • Factory と Faker によるテストデータ作成
  • データベース状態の検証と、よくある失敗例

背景:Feature Test と Unit Test の違い

Laravel のテストは、大きく Feature Test と Unit Test に分けて考えると整理しやすいです。

種類見る範囲向いている例
Feature Testルーティング、ミドルウェア、DB、レスポンスなどをまたぐ流れログイン後に投稿できるか、API が期待する JSON を返すか
Unit Test小さなクラスや関数のふるまい金額計算、値オブジェクト、サービスクラスの分岐

Feature Test は「ユーザー操作に近い流れ」を確認しやすく、Unit Test は「小さなロジック」を素早く確認しやすいです。どちらか一方に寄せるより、壊したくない境界に合わせて使い分けると運用しやすくなります。

迷ったときは、まずログイン、投稿作成、決済前の確認画面など、壊すとユーザーに見える流れを Feature Test にします。金額計算や文字列変換のように入力と出力が小さく閉じる処理は、Unit Test から始めると書きやすいです。

実装と検証

テストファイルを作成する

Laravel では Artisan でテストファイルを生成できます。Feature Test を作る場合は次のように実行します。

php artisan make:test PostCreationTest

Unit Test を作る場合は --unit を付けます。

php artisan make:test PriceCalculatorTest --unit

プロジェクトによって PHPUnit と Pest のどちらを使うかは異なります。Laravel 11 以降でも、Laravel Installer 経由か composer create-project 経由かで初期構成は変わることがあります。手元の composer.jsonrequire-devphpunit/phpunit または pestphp/pest のどちらが入っているかを確認してください。

この記事のコード例は PHPUnit 形式で示します。Pest を使っている場合は、Pest のドキュメントや Laravel 側のテスト設定に合わせて読み替えてください。

Feature Test:ログイン済みユーザーが投稿できることを確認する

Feature Test では、HTTP リクエストを発行し、ステータスコードやリダイレクト先、データベースの状態を確認できます。次の例は、ログイン済みユーザーが投稿を作成できることを確認するテストです。

この例は、/posts へ投稿するルート、posts テーブル、Post モデル、認証機能がすでにある前提です。手元のアプリに合わせて、URL やカラム名は読み替えてください。

<?php

namespace Tests\\Feature;

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

class PostCreationTest extends TestCase
{
    use RefreshDatabase;

    public function test_authenticated_user_can_create_post(): void
    {
        $user = User::factory()->create();

        $response = $this->actingAs($user)->post('/posts', [
            'title' => 'テスト投稿',
            'body' => '本文です。',
        ]);

        $response->assertRedirect('/posts');

        $this->assertDatabaseHas('posts', [
            'user_id' => $user->id,
            'title' => 'テスト投稿',
        ]);
    }
}

actingAs は認証済みユーザーとしてリクエストを実行するためのヘルパーです。HTTP テストの書き方は HTTP Tests にまとまっています。

この例で assertDatabaseHas が落ちたときは、保存処理だけを疑う前に確認順を決めると早いです。たとえば、認証ユーザー、バリデーション、リクエスト先のルート、テスト用 DB の状態を順に見ます。

Factory と Faker:テストデータを作る

Factory は、モデルのテストデータを決まった形で作る仕組みです。Faker は名前やメールアドレスなどのダミーデータを生成するために使われます。Laravel の Factory では fake() ヘルパーを使う例が一般的です。

短く言うと、Factory は「モデルを作る型」、Faker は「型の中へ入れるダミー値を作る道具」です。

<?php

namespace Database\\Factories;

use Illuminate\\Database\\Eloquent\\Factories\\Factory;
use Illuminate\\Support\\Str;

class UserFactory extends Factory
{
    public function definition(): array
    {
        return [
            'name' => fake()->name(),
            'email' => fake()->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => bcrypt('password'),
            'remember_token' => Str::random(10),
        ];
    }
}

テスト側では User::factory()->create() のように呼び出します。データベースへ保存せず、モデルインスタンスだけ作りたい場合は make() を使います。

$savedUser = User::factory()->create();
$unsavedUser = User::factory()->make();

Factory の基本は Eloquent Factories にまとまっています。Factory の状態(state)を使うと、「管理者ユーザー」「退会済みユーザー」のような条件付きデータも表現できます。

Unit Test:小さなロジックを確認する

Unit Test は、DB や HTTP をまたがない小さなロジックに向いています。たとえば、一定金額以上なら送料を無料にするクラスを考えます。

<?php

namespace App\\Services;

class PriceCalculator
{
    public function total(int $subtotal, int $shippingFee): int
    {
        if ($subtotal >= 5000) {
            return $subtotal;
        }

        return $subtotal + $shippingFee;
    }
}

テストは次のように書けます。

<?php

namespace Tests\\Unit;

use App\\Services\\PriceCalculator;
use PHPUnit\\Framework\\TestCase;

class PriceCalculatorTest extends TestCase
{
    public function test_total_includes_shipping_fee_when_subtotal_is_less_than_threshold(): void
    {
        $calculator = new PriceCalculator();

        $this->assertSame(1200, $calculator->total(1000, 200));
    }

    public function test_total_excludes_shipping_fee_when_subtotal_reaches_threshold(): void
    {
        $calculator = new PriceCalculator();

        $this->assertSame(5000, $calculator->total(5000, 200));
    }
}

Unit Test では、できるだけ入力と出力が見える形にすると読みやすくなります。DB や認証を使うものは、無理に Unit Test へ寄せないほうが自然です。Feature Test で確認する選択肢も検討します。

実装時の注意点

Feature Test でデータベースを触る場合は、テストごとに状態を戻す仕組みが必要です。上の例では RefreshDatabase を使っています。詳細は Database Testing を確認してください。

Factory の Faker は便利ですが、ランダムな値に頼りすぎると失敗時の原因が追いにくくなります。アサーションに関わる値は、テスト内で明示するほうが読みやすいです。

また、Feature Test は多くの層を通るため、Unit Test より実行時間が長くなりやすいです。重要なユーザー操作は Feature Test で守り、純粋な計算や変換は Unit Test に寄せるとバランスを取りやすくなります。

失敗例:Factory のランダム値に期待してテストが不安定になった

筆者がテストを書き始めたころ、Factory が作るランダムなメールアドレスや名前をそのまま使い、レスポンス本文の一部まで検証していたことがあります。あるとき、Faker が生成した文字列の違いで期待値がずれ、テストが落ちたり通ったりする状態になりました。

原因は、テストの主張に必要な値まで Factory 任せにしていたことです。ユーザーが作られることだけを確認したいならランダム値でも十分ですが、画面表示やレスポンス本文まで確認する場合は、期待値に使う文字列をテスト内で明示すべきでした。

$user = User::factory()->create([
    'name' => '山田 太郎',
]);

$response = $this->actingAs($user)->get('/profile');

$response->assertSee('山田 太郎');

このように、テストの読み手が「何を保証したいのか」を一目で追える値にすると、失敗したときの調査がかなり楽になります。

学び:テストは「何を壊したくないか」を言葉にする場所

テストは、コードの正しさを機械的に確認するだけではありません。チームにとって「この動きは壊したくない」と明文化する場所でもあります。

Feature Test はユーザーに近いふるまいを守り、Unit Test は小さなロジックの意図を守ります。Factory と Faker はそのためのデータ準備を楽にしますが、主張したい値まで隠してしまわないように注意が必要です。

まとめ

  • Feature Test は HTTP や DB をまたぐ流れ、Unit Test は小さなロジックの確認に向いている。
  • Factory と Faker を使うとテストデータを作りやすいが、アサーションに関わる値は明示すると読みやすい。
  • RefreshDatabase などで DB 状態を戻し、テスト同士が影響しないようにする。

次に試せること

  1. 既存の投稿作成やログイン処理を 1 つ選び、Feature Test で HTTP ステータスと DB 状態を確認する。
  2. Factory に state を 1 つ追加し、管理者ユーザーや公開済み投稿のテストデータを作る。
  3. 既存のサービスクラスから入力と出力が明確なメソッドを選び、Unit Test を 1 つ書く。

Source