Laravel 模拟外部服务
Posted
技术标签:
【中文标题】Laravel 模拟外部服务【英文标题】:Laravel mock external service 【发布时间】:2021-10-12 01:19:49 【问题描述】:我有一些类可以创建用户“集成”并使用外部 API 检查 API 凭据:
class IntegrationService
public function create(array $params)
$api = new IntegrationApi();
if (!$api->checkCredentials($params['api_key']))
throw new \Exception('Invalid credentials');
// storing to DB
return 'ok'; // just for example
IntegrationApi 类:
class IntegrationApi
public function checkCredentials(string $apiKey): bool
// some external api calls
return false; // just for example
我需要为 IntegrationService 类创建单元测试。在创建测试集成之前,我试图在我的测试中模拟 IntegrationApi 类,但我的测试因该异常而失败......
class TestIntegrationService extends TestCase
public function test_create()
$service = new IntegrationService();
$this->mock(IntegrationApi::class, function (MockInterface $mock)
$mock->shouldReceive('checkCredentials')->withArgs(['apiKey'])->once()->andReturnTrue();
);
$res = $service->create(['api_key' => '123']);
$this->assertEquals('ok', $res);
似乎 IntegrationApi 对象没有按预期模拟,但我不知道为什么。在这种情况下,我是否正确应用了对象模拟?
【问题讨论】:
【参考方案1】:您需要了解依赖注入和Service Container 概念。
首先,永远不要在 Laravel 项目中使用 new
关键字 - 通过构造函数使用依赖注入:
class IntegrationService
private IntegrationApi $api;
public function __construct(IntegrationApi $api)
$this->api = $api;
public function create(array $params)
if (!$this->api->checkCredentials($params['api_key']))
throw new \Exception('Invalid credentials');
// storing to DB
return true; // never use magic strings. But in this case - void be preferred - throw exceptions on error and return nothing
在这种情况下进行测试
public function setUp()
$this->mockApi = Mockery::mock(IntegrationApi::class);
$this->service = new IntegrationService($this->mockApi);
public function testCreateOk()
$this->mockApi->shouldReceive('checkCredentials')->withArgs(['apiKey'])->once()->andReturnTrue();
$this->assertTrue($this->service->create(['apiKey']));
public function testCreateError()
$this->mockApi->shouldReceive('checkCredentials')->withArgs(['apiKey'])->once()->andReturnFalse();
$this->expectsException(Exception::class);
$this->service->create(['apiKey']);
【讨论】:
【参考方案2】:当你想添加测试时,你永远不能直接使用new
,它硬连线到实现类,所以你的模拟将不会被使用。
你需要使用依赖注入/服务容器:
class IntegrationService
public function create(array $params)
$api = app(IntegrationApi::class);
这允许将实现(从app
函数动态返回)交换到模拟对象。
如果这段代码在测试上下文之外运行时没有绑定任何内容,Laravel 将负责调用 new
。
正如 Maksim 在 cmets 中指出的那样,构造函数注入是避免使用 app()
的另一种方式:
class IntegrationService
protected $api;
public function __construct(IntegrationApi $api)
$this->api = $api;
public function create(array $params)
if (!$this->api->checkCredentials ...
n.b.:您不需要手动提供/定义这些参数/它们的位置来获得您的服务。如果你还在 Controller 中使用 app()
/injection 请求服务,Laravel 会自动处理(使用反射)。
【讨论】:
为什么是 app()?构造函数注入是清晰而漂亮的方式 构造函数注入还需要他们用这些方法(他们应该 obv)实例化IntegrationService
,并添加一个构造函数开始。但我同意它会更干净。以上是关于Laravel 模拟外部服务的主要内容,如果未能解决你的问题,请参考以下文章
在 Laravel 中运行功能测试时如何模拟服务(或服务提供者)?