Laravelのテストについて、tests/Unitとtests/Featureに分かれているものの、
どっちに何を書くべきかが曖昧になりやすい。
特に以下のような疑問点はよくある。
- RepositoryのテストはUnit? Feature?
- ControllerのUnitTestは必要?
- Middlewareのテストはどっち?
- ForemRequestのUnitTestは必要?
- LaravelのUnitTestはDBにアクセスしていいの?
この記事ではLaravelのレイヤー構造に沿って、"どの層をどのテストで保証すべきか"を整理する。
Laravel のレイヤー構造(ざっくり図解)
app/
├── Application ← HTTP レイヤー
│ └── Http
│ ├── Controllers ← Controller
│ ├── Middleware ← Middleware
│ ├── Requests ← FormRequest
│ └── Resources ← Resource
│
├── Domain ← ドメイン層
│ ├── Models ← Entity / Eloquent Model
│ └── Repositories ← Repository Interface
│
├── Infrastructure ← 実装層(FeatureTest)
│ └── Repositories
│ ├── Eloquent ← Repository 実装(DB)
│ └── Http ← 外部 API クライアント
UnitTest とは何か
UnitTest は 「HTTP を通さず、純粋なロジックをテストする」 ためのテスト。
特徴
- HTTP リクエストを使わない
- DB を使わない(※Laravel では例外的に使うケースもある)
- モックを使って依存を切り離す
- 小さな単位のロジックを高速に検証する
何をテストする? - Service / UseCase のビジネスロジック
- 条件分岐
- 例外処理
- 複数 Repository の組み合わせ
- Model のアクセサ・スコープなどのロジック
目的
- ロジックの正しさを高速に保証すること
- 外部依存を切り離して、壊れにくいテストにすること
FeatureTest とは何か(Laravel 実務における定義)
FeatureTest は 「HTTP レイヤーを含む、実際の動作を統合的にテストする」 ためのテスト。
特徴
- HTTP リクエストを実際に投げる
- DB を使う(RefreshDatabase)
- ルーティング・ミドルウェア・FormRequest・Resource などを含めて動作確認
- 実際のアプリの挙動に最も近いテスト
何をテストする? - Controller の動作
- FormRequest のバリデーション(422)
- Middleware の認証・ヘッダー検証
- Resource の JSON 形式
- Repository の DB クエリ(Eloquent)
- 実際の HTTP レスポンス
目的
- アプリケーション全体の動作を保証すること
- HTTP レイヤーの仕様を壊れにくくすること
レイヤーごとの最適なテスト戦略(結論)
| レイヤー | テスト | 理由 |
|---|---|---|
| Controller | Feature | HTTP レイヤーの責務(ルート・ミドルウェア・FormRequest・Resource)を保証するため |
| FormRequest | Feature | バリデーションは HTTP を通して動くため |
| Repository | Feature | クエリの正しさは実 DB でしか保証できない |
| Service | Unit | ビジネスロジックを純粋にテストするため |
| Model | Unit | アクセサ・スコープなどロジックがある場合のみ |
| Middleware | Feature | HTTP レイヤーでしか動作を保証できない |
| Resource | Feature | JSON 構造は HTTP レスポンスで確認するのが自然 |
各レイヤーごとに、なぜそのテストが最適なのか(詳しい理由)
Controller (FeatureTestが最適)
Controller の責務は HTTP 入出力の接着剤に徹すること。
実際に保証すべき内容は次の通り。
- ルーティングが正しく紐づいているか
- ミドルウェアが正しく動作しているか
- FormRequest が発火しているか
- Resource が正しい JSON を返すか
- ステータスコードが正しいか
これらは HTTP を通さないと検証できないため、FeatureTest が最適。
Controller を UnitTest すると、
Repository・FormRequest・Resource を全部モック化する必要があり、
「Controller の薄い責務」に対してコストが大きすぎる。
FormRequest(FeatureTest が最適)
FormRequest の責務は HTTP リクエストのバリデーション。
- required / string / email などのルール
- authorize() の判定
- バリデーションエラー時の 422 レスポンス
- エラーメッセージの JSON 形式
これらは HTTP レイヤーでしか動作しないため、
FeatureTest で /api/... に POST して 422 を確認するのが最も自然。
UnitTest で FormRequest を new して validate() を叩く方法もあるが、
実際の動作と乖離しやすく、保守性が低い。
Repository(FeatureTest が最適)
Repository の責務は DB クエリそのもの。
- create() が正しく保存されるか
- find() が正しいレコードを返すか
- where 条件が正しいか
- リレーションが eager load されるか
これらは 実 DB を使わないと正しさを保証できない。
Repository をモック化した UnitTest は、
「モックが呼ばれた」ことしか検証できず、
クエリの正しさは一切保証できない。
そのため、Repository は FeatureTest(DBあり)が最適。
Service / UseCase(UnitTest が最適)
Service / UseCase の責務は ビジネスロジック。
- 条件分岐
- 例外処理
- 複数 Repository の組み合わせ
- イベント発火
- トランザクション制御
これらは DB や HTTP に依存しない純粋ロジックとして扱うべき。
Repository をモック化し、
「ロジックだけ」を高速にテストできる UnitTest が最適。
Model(必要なら UnitTest)
Eloquent Model はフレームワークの責務が大きいため、
基本的にはテスト不要。
ただし、次のようなロジックがある場合は UnitTest する価値がある。
- アクセサ / ミューテタ
- カスタムスコープ
- 独自メソッド
- 複雑な属性変換
Model のロジックは DB を使わずにテストできるケースが多い。
Middleware(FeatureTest が最適)
Middleware の責務は HTTP リクエストの前後処理。
- 認証・認可
- ヘッダー検証
- レスポンスの書き換え
- リクエストの拒否(403 など)
これらは HTTP を通さないと正しく動作しないため、
FeatureTest が最適。
UnitTest で Request/Response を new してテストする方法もあるが、
実際の動作と乖離しやすい。
Resource(FeatureTest が最適)
Resource の責務は HTTP レスポンスの JSON 変換。
- JSON の構造
- ネストされたデータ
- null の扱い
- コレクションの整形
これらは 実際の HTTP レスポンスで確認するのが最も自然。
UnitTest で Resource::make() を叩く方法もあるが、
Controller → Resource の流れを保証できない。
まとめ
色々説明してきたが結論としてはシンプルに以下の通り
【UnitTest】
- Service / UseCase(ビジネスロジック)
- Model のロジック(アクセサ・スコープなど)
【FeatureTest】
- Controller
- FormRequest
- Middleware
- Resource
- Repository(DBクエリ)
HTTPとDBに触れるものはFeature, それ以外はUnitに機械的に分類するだけで、
各レイヤーの責務とテストの目的が一致しており、
Laravel のアーキテクチャと完全に整合する。