【Laravel】テストの書き方入門|Feature Test・Unit Test・FactoryとFakerの使い方
動いている画面を少し直しただけのつもりが、ログイン後の導線や保存処理を壊してしまうことがあります。手元でブラウザを開いて確認しても、毎回同じ手順を漏れなく試すのは難しく、修正が増えるほど不安が残りがちです。
Laravel のテストは、この不安を小さくするための仕組みです。この記事では、Feature Test と Unit Test の違いを整理します。Factory と Faker によるデータ作成、HTTP レスポンスやデータベース状態の確認も、最小のコード例で扱います。
細かな API や標準のテストランナーは Laravel のメジャーバージョンやプロジェクト作成時の選択で変わることがあります。手元の composer.json の laravel/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.json の require-dev に phpunit/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 つ選び、Feature Test で HTTP ステータスと DB 状態を確認する。
- Factory に state を 1 つ追加し、管理者ユーザーや公開済み投稿のテストデータを作る。
- 既存のサービスクラスから入力と出力が明確なメソッドを選び、Unit Test を 1 つ書く。




