Laravel 模拟外部服务
Laravel mock external service
我有一些 class 创建用户“集成”并使用外部 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:
class IntegrationApi
{
public function checkCredentials(string $apiKey): bool
{
// some external api calls
return false; // just for example
}
}
我需要为 IntegrationService class 创建单元测试。在创建测试集成之前,我试图在我的测试中模拟 IntegrationApi class,但我的测试因该异常而失败...
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 对象没有像预期的那样模拟,但我不知道为什么。在这种情况下我是否正确应用了对象模拟?
当你想添加测试时你永远不能直接使用 new
,它硬连接到实现 class 所以你的模拟不会被使用。
您需要使用依赖注入/服务容器:
class IntegrationService
{
public function create(array $params)
{
$api = app(IntegrationApi::class);
这允许将实现(从 app
函数动态返回)交换到模拟对象。
如果此代码在测试上下文之外运行时没有任何绑定,Laravel 将负责调用 new
。
正如 Maksim 在评论中指出的那样,构造函数注入是避免使用 app()
:
的另一种方式
class IntegrationService
{
protected $api;
public function __construct(IntegrationApi $api)
{
$this->api = $api;
}
public function create(array $params)
{
if (!$this->api->checkCredentials ...
n.b.: 您不需要手动 provide/define 这些 args/their 职位来获得您的服务。如果您还在控制器中使用 app()
/注入请求服务,Laravel 将自动处理(使用反射)。
您需要了解依赖注入和 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']);
}
我有一些 class 创建用户“集成”并使用外部 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:
class IntegrationApi
{
public function checkCredentials(string $apiKey): bool
{
// some external api calls
return false; // just for example
}
}
我需要为 IntegrationService class 创建单元测试。在创建测试集成之前,我试图在我的测试中模拟 IntegrationApi class,但我的测试因该异常而失败...
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 对象没有像预期的那样模拟,但我不知道为什么。在这种情况下我是否正确应用了对象模拟?
当你想添加测试时你永远不能直接使用 new
,它硬连接到实现 class 所以你的模拟不会被使用。
您需要使用依赖注入/服务容器:
class IntegrationService
{
public function create(array $params)
{
$api = app(IntegrationApi::class);
这允许将实现(从 app
函数动态返回)交换到模拟对象。
如果此代码在测试上下文之外运行时没有任何绑定,Laravel 将负责调用 new
。
正如 Maksim 在评论中指出的那样,构造函数注入是避免使用 app()
:
class IntegrationService
{
protected $api;
public function __construct(IntegrationApi $api)
{
$this->api = $api;
}
public function create(array $params)
{
if (!$this->api->checkCredentials ...
n.b.: 您不需要手动 provide/define 这些 args/their 职位来获得您的服务。如果您还在控制器中使用 app()
/注入请求服务,Laravel 将自动处理(使用反射)。
您需要了解依赖注入和 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']);
}