You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 04:55:00