Laravel功能测试中捕获所有外部HTTP请求的方法
在Laravel功能测试中伪造外部服务HTTP响应
问题背景
项目里的控制器和任务会调用Stripe、Mailchimp这类外部服务API,写功能测试时,真实的外部请求不仅拖慢测试速度,还会往关联账户里塞测试数据。不想通过重写业务代码来伪造请求(避免偏离测试真实代码的初衷),希望在发起控制器HTTP请求时,捕获所有外部HTTP请求并返回伪造响应。
举个例子,有这样的控制器方法:
class ProgramController extends Controller { public function invite(ProgramInviteRequest $request): JsonResponse { $mailchimp = new ApiClient(); $mailchimp->setConfig([ 'apiKey' => config('services.mailchimp.api_key'), 'server' => config('services.mailchimp.server_prefix'), ]); // 发送邀请前确认用户在列表中 $response = $mailchimp->lists->getListMemberTags($request->list_id, md5($request->email)); $has_tag = collect($response->tags)->contains(function ($tag) { return $tag->name == 'Program Member'; }); if (!$has_tag) { return response()->json(['message' => 'Not a program member.'], 404); } InviteToProgram::dispatch($request->email); return response()->json(['message' => 'Invitation sent.']); } }
想要在功能测试里伪造Mailchimp的getListMemberTags请求,返回假数据来测试404场景:
$response = $this->post(route('program.invite'), [ 'email' => fake()->safeEmail, 'list_id' => 'fake_list_id', ]); $response->assertStatus(404);
解决方案
方法1:用Laravel HTTP客户端伪造(适配基于Guzzle的SDK)
Laravel的Http facade提供的fake()方法,能拦截所有通过Guzzle发起的HTTP请求——大部分第三方SDK(比如Mailchimp、Stripe)都是基于Guzzle实现的,所以这个方法不用改业务代码就能生效。
测试代码示例:
use Illuminate\Support\Facades\Http; public function test_invite_returns_404_when_not_program_member() { // 伪造Mailchimp API的响应 Http::fake([ // 匹配Mailchimp的API域名和请求路径 '*mailchimp.com/3.0/lists/*/members/*/tags' => Http::response([ 'tags' => [ ['name' => 'Regular Member', 'status' => 'active'] ] ], 200), ]); $response = $this->post(route('program.invite'), [ 'email' => fake()->safeEmail, 'list_id' => 'fake_list_id', ]); $response->assertStatus(404); }
如果需要更精准的请求匹配,还能用闭包判断请求细节:
Http::fake(function ($request) { if (str_contains($request->url(), 'getListMemberTags')) { return Http::response([ 'tags' => [] ], 200); } // 其他请求可返回默认响应或抛出异常 return Http::response([], 404); });
方法2:Mock第三方SDK实例(精准控制方法调用)
如果SDK支持依赖注入,直接Mock SDK的客户端类,能更精准地控制方法返回值,避免HTTP层面的拦截。
步骤1:重构控制器(可选,提升可测试性)
把Mailchimp客户端通过构造函数注入:
class ProgramController extends Controller { protected $mailchimp; public function __construct(ApiClient $mailchimp) { $this->mailchimp = $mailchimp; $this->mailchimp->setConfig([ 'apiKey' => config('services.mailchimp.api_key'), 'server' => config('services.mailchimp.server_prefix'), ]); } public function invite(ProgramInviteRequest $request): JsonResponse { $response = $this->mailchimp->lists->getListMemberTags($request->list_id, md5($request->email)); // 后续逻辑保持不变 } }
步骤2:测试中Mock SDK实例
use MailchimpMarketing\ApiClient; use Mockery; public function test_invite_returns_404_when_not_program_member() { // Mock Mailchimp客户端 $mailchimpMock = Mockery::mock(ApiClient::class); // 模拟getListMemberTags方法的返回数据 $mailchimpMock->lists->shouldReceive('getListMemberTags') ->with('fake_list_id', Mockery::any()) ->andReturn((object)[ 'tags' => [ (object)['name' => 'Regular Member'] ] ]); // 将Mock实例绑定到Laravel容器 $this->app->instance(ApiClient::class, $mailchimpMock); $response = $this->post(route('program.invite'), [ 'email' => fake()->safeEmail, 'list_id' => 'fake_list_id', ]); $response->assertStatus(404); }
方法3:用Guzzle中间件拦截请求
如果不想依赖Laravel的Http facade,可以直接给Guzzle加中间件拦截请求并返回伪造响应,适合需要深度定制HTTP逻辑的场景。
测试代码示例:
use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\HandlerStack; use GuzzleHttp\Psr7\Response; use MailchimpMarketing\ApiClient; public function test_invite_returns_404_when_not_program_member() { // 创建Guzzle的Mock Handler $mock = new MockHandler([ new Response(200, [], json_encode([ 'tags' => [['name' => 'Regular Member']] ])), ]); $handlerStack = HandlerStack::create($mock); $client = new \GuzzleHttp\Client(['handler' => $handlerStack]); // 初始化Mailchimp客户端并替换内置的Guzzle实例 $mailchimp = new ApiClient(); $mailchimp->setConfig([ 'apiKey' => config('services.mailchimp.api_key'), 'server' => config('services.mailchimp.server_prefix'), ]); $mailchimp->setHttpClient($client); $this->app->instance(ApiClient::class, $mailchimp); $response = $this->post(route('program.invite'), [ 'email' => fake()->safeEmail, 'list_id' => 'fake_list_id', ]); $response->assertStatus(404); }
总结
- 想最小化代码改动,优先用方法1(Laravel Http::fake()),自动拦截所有Guzzle请求,无需修改业务代码。
- 需要精准控制SDK方法调用,选方法2(Mock SDK实例),建议配合依赖注入使用,提升代码可维护性。
- 要深度定制HTTP拦截逻辑,用方法3(Guzzle中间件)。
内容的提问来源于stack exchange,提问作者eComEvo
相关产品推荐
相关产品推荐

