我有一些类可以创建用户“集成”并使用外部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对象似乎没有像预期的那样被模拟,但我不知道为什么。在这种情况下,我正确地应用了对象模拟吗?
发布于 2021-08-07 15:35:46
您需要理解依赖注入和服务容器概念。
首先,永远不要在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']);
}
发布于 2021-08-07 15:14:12
您永远不能直接使用new
当您想要添加测试,它硬连接到实现类,这样您的模拟就不会被使用。
您需要使用依赖注入/服务容器:
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 ...
注:您不需要手动提供/定义这些args/它们的位置来获得您的服务。如果您还使用控制器中的app()
/injection请求服务,Laravel将自动处理该服务(使用反射)。
https://stackoverflow.com/questions/68693565
复制相似问题