首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Laravel模拟外部服务

Laravel模拟外部服务
EN

Stack Overflow用户
提问于 2021-08-07 15:07:20
回答 2查看 815关注 0票数 0

我有一些类可以创建用户“集成”并使用外部API检查API凭据:

代码语言:javascript
运行
复制
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类:

代码语言:javascript
运行
复制
class IntegrationApi
{
    public function checkCredentials(string $apiKey): bool
    {
        // some external api calls

        return false; // just for example
    }
}

我需要为IntegrationService类创建单元测试。在创建测试集成之前,我试图在我的测试中模拟IntegrationApi类,但是我的测试失败了.

代码语言:javascript
运行
复制
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对象似乎没有像预期的那样被模拟,但我不知道为什么。在这种情况下,我正确地应用了对象模拟吗?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-08-07 15:35:46

您需要理解依赖注入和服务容器概念。

首先,永远不要在Laravel项目中使用new关键字--通过构造函数使用依赖注入:

代码语言:javascript
运行
复制
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
    }
} 

在这种情况下的测试就像

代码语言:javascript
运行
复制
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
EN

Stack Overflow用户

发布于 2021-08-07 15:14:12

您永远不能直接使用new当您想要添加测试,它硬连接到实现类,这样您的模拟就不会被使用。

您需要使用依赖注入/服务容器:

代码语言:javascript
运行
复制
class IntegrationService
{
    public function create(array $params)
    {
        $api = app(IntegrationApi::class);

这允许将实现(从app函数动态返回)交换到模拟对象。如果该代码在测试上下文之外运行时没有绑定,Laravel将负责调用new

正如Maksim在注释中所指出的,构造函数注入是避免使用app()的另一种方法。

代码语言:javascript
运行
复制
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将自动处理该服务(使用反射)。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68693565

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档