Slim 4 + PHPUnit + Mockery で始めるユニットテスト入門|DIとモックでテストを自動化
この記事では Slim 4 + PHPUnit + Mockery を組み合わせ、依存性注入(DI)とサービス層のモック化に焦点を当てたユニットテスト戦略を解説します。
「Slim だからテストは後回しでいい」と考えがちな開発者に向け、導入ハードルを下げつつテスト網を拡充する具体策を提示します。
Slim 4 と PSR‑11 コンテナの活用 ― DI 設計の前提を固める
Slim 4 以降は AppFactory::setContainer()
によって PSR‑11 準拠のコンテナを差し替えられます。つまり $container->get()
をルートハンドラ内で直に呼ぶよりも、コンストラクタインジェクションで依存を解決する方が自然でテスト可能性も高まります。
例えばリポジトリクラスを直接生成すると「本番では DB、テストではモック」という切り替えが難しくなりますが、ArticleRepositoryInterface
を注入すれば Mockery 由来のテストダブルへ瞬時に差し替え可能です。
コンテナ直取得 vs コンストラクタインジェクション
観点 | コンテナ直取得 | コンストラクタインジェクション |
---|---|---|
テスト容易性 | 低い | 高い |
可読性 | 取得箇所が散在 | 依存が明示的 |
実装工数 | 少 | やや多 |
多少コードが増える代わりに「いつでも安全にリファクタできる」という保険を手に入れられます。
PHPUnit + Mockery のセットアップ手順【PHP 8.3 対応】
2025年5月時点で slim/psr7
は PHP 8.0 以降が必須です。テスト用の phpunit/phpunit
(10.x 系)と mockery/mockery
(2.x 系)は Composer で追加します。
composer require --dev phpunit/phpunit ^10 mockery/mockery ^2
tests/bootstrap.php
にはオートローダと、Mockery::globalHelpers()
を使う場合のみ次を追加します。
グローバルヘルパーは mock()
を手軽に呼べる利点がありますが、名前空間汚染を避けたい場合は省略しても問題ありません。
特に、PSR-4 に準拠したテスト構成では Mockery::mock()
を明示的に使った方が可読性と保守性が高いこともあります。
require __DIR__ . '/../vendor/autoload.php';
// 任意: Procedural ヘルパーを使うなら有効化
Mockery::globalHelpers();
PHPUnit の xml
ではユニットテスト用ディレクトリを tests/Unit
と分け、E2E や API テストと明確に住み分けておくと CI が高速化します。
サービス層をモック化したユニットテスト例 ― DB を切り離す
本番では MySQL に書き込む ArticleRepository
をモックに差し替え、ビジネスロジックのみを検証するコード例を示します。
namespace Tests\Unit;
use App\Service\ArticleService;
use App\Repository\ArticleRepositoryInterface;
use Mockery;
use PHPUnit\Framework\TestCase;
class ArticleServiceTest extends TestCase
{
protected function tearDown(): void
{
Mockery::close();
}
```
public function testPublishReturnsTrueOnSuccess(): void
{
$repo = Mockery::mock(ArticleRepositoryInterface::class);
$repo->shouldReceive('save')
->once()
->andReturn(true);
// ArticleService は ArticleRepositoryInterface を受け取る設計
$service = new ArticleService($repo);
$result = $service->publish(['title' => '新記事']);
$this->assertTrue($result);
}
```
}
ここでは shouldReceive()
で呼び出し回数と戻り値を明示しています。これにより「DB 接続に失敗したらどうなるか」といった分岐も、andThrow()
を差し替えるだけでテストできます。
テストシナリオを網羅する 3 ステップ
まず「正常」「例外」「空レスポンス」の 3 パターンを洗い出します。次に外部依存(DB、メール、外部 API)をすべてモックに置き換え、最後に 入出力の最小関係だけをアサートしましょう。これで「実装をリファクタしたらテストが壊れる」事態を大幅に減らせます。
よくある落とし穴と対策
MockeryException: No matching handler found
期待シグネチャと実呼び出しがずれている場合に発生します。引数の順序・型・回数を再確認し、メソッドチェーンがある場合は with()
で厳密に指定しましょう。
非公開メソッドをテストしたくなる問題
プライベートメソッドだけを直接テストするのは避けるのが原則です。そもそもテストしたい処理があるなら、小さなユーティリティクラスへ抽出しパブリック API として公開すると見通しが良くなります。設計面の責務分割を意識すれば「private をむりやり叩く」誘惑は消えます。
DI コンテナの循環参照
双方向依存はテストだけでなく本番でも地雷です。上位層→下位層の一方向依存を守り、インターフェースだけを共有する構成にしましょう。
モダンPHPで作るシンプルREST API:Slimフレームワークチュートリアル
まとめ ― 小さく始め、継続的に改善するテスト戦略
PHPUnit + Mockery は現在でも多くの現場で使われている堅実な組み合わせです。テストの文化が根づいていないチームでも、まずこの構成から始めることで「書ける・読める・直せる」安心感を得られます。
Slim 4 と DI を組み合わせれば、マイクロフレームワークでもテストファーストの開発サイクルを構築できます。まずはサービス層のモック化ユニットテストから着手し、CI に組み込んで失敗を 早期に可視化しましょう。
小さく回しながらカバレッジを広げれば、リリースを恐れない開発が実現します。今日書く1本のテストが、半年後のあなたを救うはずです。