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

Laravel下PHPUnit Mock Twilio时首个mock生效第二个失效如何解决?

问题根本原因
  • 业务代码中执行new Client()直接替换了原本注入的模拟Twilio Client实例,后续调用的是未被模拟的真实实例,因此发起真实请求返回404。
  • 使用overload:前缀模拟类时,Mockery生成的模拟类默认不会继承原类的父类,因此你模拟的NewKeyList不被识别为ListResource的子类,触发返回类型错误。
  • 链式调用模拟未生效是因为你没有将模拟的Client实例绑定到Laravel服务容器,控制器拿到的还是真实Client实例,另外你代码里的拼写错误seld::$mockTwilioAccount也会影响模拟生效。
推荐解决方案(最小改造成本+可维护性最高)

核心思路是消除业务代码里的硬编码new Client()调用,遵循依赖注入规范,方便测试模拟。

步骤1:封装Twilio客户端工厂

新建工厂类统一处理Twilio Client实例化:

<?php
namespace App\Factories;

use Twilio\Rest\Client;

class TwilioClientFactory
{
    public function make(string $sid, string $authToken): Client
    {
        return new Client($sid, $authToken);
    }
}

步骤2:改造控制器逻辑

将工厂注入控制器,替换硬编码的new逻辑:

// 控制器构造方法
public function __construct(\Twilio\Rest\Client $twilioClient, \App\Factories\TwilioClientFactory $clientFactory)
{
    $this->twilioClient = $twilioClient;
    $this->clientFactory = $clientFactory;
}

// create方法修改替换实例的逻辑
public function create(Request $request)
{
    // some code
    $twilioAccount = $this->twilioClient->api->v2010->accounts->create($account_options);
    // 替换原本的new Client为工厂调用
    $this->twilioClient = $this->clientFactory->make($twilioAccount->sid, $twilioAccount->auth_token);
    $apiKey = $this->twilioClient->newKeys->create($api_key_options);
    // some more code
}

步骤3:重写测试逻辑

直接模拟工厂和两次调用的Client实例即可:

public function testSuccessfullyCreate()
{
    // 模拟第一次调用的Client,用于创建账号
    $firstClientMock = \Mockery::mock(\Twilio\Rest\Client::class);
    $mockAccount = \Mockery::mock(\Twilio\Rest\Api\V2010\AccountInstance::class);
    $mockAccount->sid = 'validAccountSid';
    $mockAccount->auth_token = 'validAccountAuthToken';
    $firstClientMock->shouldReceive('api->v2010->accounts->create')
        ->once()
        ->andReturn($mockAccount);

    // 模拟第二次调用的Client,用于创建ApiKey
    $secondClientMock = \Mockery::mock(\Twilio\Rest\Client::class);
    $mockApiKey = \Mockery::mock(\Twilio\Rest\Api\V2010\Account\NewKeyInstance::class);
    $mockApiKey->sid = 'validApiKeySid';
    $secondClientMock->shouldReceive('newKeys->create')
        ->once()
        ->andReturn($mockApiKey);

    // 模拟工厂,返回第二个模拟Client
    $factoryMock = \Mockery::mock(\App\Factories\TwilioClientFactory::class);
    $factoryMock->shouldReceive('make')
        ->once()
        ->with('validAccountSid', 'validAccountAuthToken')
        ->andReturn($secondClientMock);

    // 将模拟实例绑定到Laravel服务容器
    $this->app->instance(\Twilio\Rest\Client::class, $firstClientMock);
    $this->app->instance(\App\Factories\TwilioClientFactory::class, $factoryMock);

    // 发起测试请求
    $this->json('POST', $uri, [], $authentication_token);
    $this->seeStatusCode(201);
}
可选方案:不改业务代码的临时解决方法

如果不方便改造业务代码,可以用Mockery的别名模拟拦截所有新创建的Client实例,需要在测试类头部加以下注解:

/**
 * @runTestsInSeparateProcesses
 * @preserveGlobalState disabled
 */

测试时直接模拟alias:Twilio\Rest\Client即可,缺点是测试需要跑在单独进程,执行速度较慢。

如果要解决原本的NewKeyList类型错误,修改模拟写法指定继承父类即可:

self::$mockTwilioNewKey = \Mockery::mock('overload:Twilio\Rest\Api\V2010\Account\NewKeyList')
    ->shouldExtend(\Twilio\ListResource::class)
    ->getMock();

内容的提问来源于stack exchange,提问作者DreamyT

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 12:54:04