v13.39.0 の実装を確認しています。基本操作はHTTPクライアント、検証環境の準備はパッケージのテストを参照してください。
Factoryは共有し、PendingRequestは毎回作る
LaravelのHTTPクライアントには、異なる責任を持つオブジェクトがあります。FoundationServiceProvider はFactoryをsingletonとして登録し、Http ファサードも同じクラス名を使ってコンテナから解決します。そのため、通常のLaravelアプリケーションでは、コンテナから受け取ったFactoryに対する通信も Http::fake() で検証できます。ファサードを直接使う設計も可能ですが、このページでは依存関係を明示するためFactoryを注入します。
Factoryに対する baseUrl() などの通常のリクエスト設定メソッドは、__call() から新しいPendingRequestへ転送されます。createPendingRequest() は生成時点のfake用コールバックとstray requestの制限を渡し、newPendingRequest() は共通ミドルウェアと共通オプションを適用します。
API操作を小さなクライアントにまとめる
架空のカタログAPIから商品を取得するAcme\Catalog パッケージを例にします。src/CatalogClient.php はFactoryとベースURLだけを保持し、リクエストは product() の実行中に作ります。
product(42, 'ja') の後に product(42) を呼んでも、前の呼び出しで追加した Accept-Language は残りません。利用側アプリケーションがFactoryに設定した共通設定は、引き続き各リクエストへ適用されます。
- パッケージ固有の設定は、新しく作ったPendingRequestへ適用します。パッケージの初期化で
Http::globalOptions()を設定すると、無関係な通信にも影響するため避けます。 - コンストラクターでは送信しません。サービスの解決、Artisanの起動、設定ファイルの公開だけで外部APIへ接続しない設計にします。
throw()は4xx・5xxをRequestExceptionにします。接続失敗やタイムアウトのConnectionExceptionと区別し、失敗を空配列などの成功値に置き換えません。json(flags: JSON_THROW_ON_ERROR)は不正なJSONをJsonExceptionにします。例では配列かどうかだけを検査しているため、実際のAPIでは必須フィールドやデータ型も検証します。
サービスプロバイダーで登録する
パッケージのconfig/catalog.php に初期設定を用意します。ここにHTTPクライアントやクロージャを保存せず、設定キャッシュへ格納できる値だけを置きます。
src/CatalogServiceProvider.php は設定をマージし、クライアントを解決するときに共有Factoryを渡します。Factoryそのもののバインディングは上書きしません。
config/acme-catalog.php のベースURLを実際のAPIへ変更します。例の catalog.example.test は説明とfake用の架空のホストです。
bind() を使っていますが、クライアントを保持する側がsingletonなら、その参照まで毎回作り直されるわけではありません。呼び出し固有の言語、認証情報、リクエストオブジェクトをプロパティに保存しないことが重要です。詳細はパッケージサービスのライフサイクル設計を参照してください。
設定の上書きとデプロイ時のキャッシュ更新はパッケージ設定のマージとキャッシュで扱っています。ベースURLは信頼できるアプリケーション設定として管理し、リクエストの入力から接続先を自由に指定させないでください。
本物のクライアントをfakeで検証する
クライアント自体をモックすると、共有Factoryを利用しているか、URLやヘッダーを正しく組み立てているかは確認できません。パッケージのテストでは、本物のCatalogClient をコンテナから解決し、HTTP応答だけをfakeにします。
次はOrchestra Testbenchのテスト環境で実行する例です。各テストの setUp() で preventStrayRequests() を有効にします。通信するテストでは想定URLにだけ応答を設定し、'*' のフォールバックを置かないことで、URLの誤りも検出できます。
assertSent() は一致するリクエストが1件以上あることの検証です。意図しない追加送信を見逃さないよう assertSentCount() も組み合わせます。assertSentInOrder() は件数と順序を検証するため、同じクライアントを続けて使ったときの状態分離にも利用できます。
ガードの設定は parent::setUp() の後です。このテストだけでプロバイダー起動中の通信まで検証できるわけではないため、登録と起動では通信しない設計を維持します。
fakeで確認できるのは、クライアントが組み立てたリクエストと、用意した応答に対する処理です。実サービスの認証、TLS接続、実際のタイムアウト時間、外部APIの仕様変更まで保証するものではありません。外部接続を伴う検証は、明示的に準備した検証環境で別に実施します。
長期保守で追加する検証
共有Factoryと各PendingRequestの責任を分けておくと、利用側が通常のLaravelのテスト方法をそのまま使えます。独自のfake機構を先に作るよりも、まず共有Factoryを通る実装と、それを確認するテストを用意してください。
参照した一次情報
- Laravel公式ドキュメント: HTTPクライアント
- Laravel公式ドキュメント: パッケージ開発
- Framework v13.39.0: Factoryのfake・createPendingRequest・__call
- Framework v13.39.0: PendingRequestの設定とstub処理
- Framework v13.39.0: ResponseのthrowとJSONデコード
- Framework v13.39.0: Httpファサードの解決先とfake
- Framework v13.39.0: FoundationServiceProviderのsingleton登録
- Laravel公式テスト: HttpClientTest