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
相关产品推荐
相关产品推荐

