【问题标题】:How to test a service that interacts with a 3rd party API?如何测试与第 3 方 API 交互的服务?
【发布时间】:2020-01-16 14:51:07
【问题描述】:

我有以下要测试的服务:

namespace App\Service;

class ApiManager
{
   public function getProjects()
   {
      $projects = $this->pager->fetchAll(
                $this->client->api('projects'),
                'all',
                [['simple' => true]]
      );
   }
}

该服务使用Gitlab API bundle for PHP。所以 $projects 中的数据是这样的:

[
   0 => [
           'id' => 1,
           'title' => 'Project #1',
           'description' => 'Project description...'
        ],
   1 => [
           'id' => 2,
           'title' => 'Project #2',
           'description' => 'Project description...'
        ],
]

当然,我不想使用来自 API 的真实数据进行测试。如何在 getProjects 中模拟从 HTTP 请求返回的数据?

【问题讨论】:

  • “HTTP请求返回的数据”是什么类型的?
  • HTTP请求返回JSON格式的数据
  • 以 JSON 格式返回数据.. 那么你能创建一个类似的 JSON 来测试吗?
  • 实际上(好的)第 3 方 API 应该有一个测试模式
  • 也许该服务提供了一些沙盒环境?一些测试帐户?如果不是,您必须以某种方式获得 api 将返回给您的响应并直接加载它而不是调用 api。

标签: php symfony testing functional-testing


【解决方案1】:

public function getProjects() 外部化到您自己的服务中,例如ProjectService 并将其注入您的 ApiManager。

创建两个版本的 ProjectService:ProjectService 和 ProjectMockService,第一个当然是用于生产,第二个应该返回您的模拟值(无论您在此处需要什么)。

然后maintain services.yaml for prod and test environments。根据活动环境,将注入正确版本的 ProjectService。

【讨论】:

    【解决方案2】:

    我认为你的问题的答案是Dependency Injection

    简单来说,依赖注入是一种设计模式,有助于避免某些代码或软件的硬编码依赖。

    资源:

    What is dependency injection?

    【讨论】:

      【解决方案3】:

      理想情况下,您的应用程序应该将来自第三方 API 的数据转换为包含在您自己的应用程序中的模型。然后,您的应用程序应该只使用此模型的实例,而不是使用从 API 返回的数据。

      这种抽象级别使测试更容易,因为只有 一个 使用 API 数据的地方:在某种映射中。服务类可以从 API 获取数据,然后映射器将响应转换为域对象。

      针对您的实际问题,我不会真正测试对 API 的 HTTP 调用。测试网络调用会使您的测试套件变慢,并且在没有网络连接的环境中会失败。相反,我会使用来自 API 的预保存或虚拟响应来测试映射器。如果 API 在某个时候开始以不同的“形状”返回数据,那么您的代码中只有一个地方需要更改(映射层)。

      【讨论】:

        【解决方案4】:

        在这种特定情况下,您不需要任何花哨的东西。您已经将 Pager 作为依赖项(我希望您通过 __construct() 接收)。 如果是这种情况,请继续模拟 $this->pager->fetchAll() 以对您的方法进行单元测试。

        如下所示:

        ApiManagerTest.php

        class ApiManagerTest extends TestCase
        {
            private $pager;
        
            private $apiManager;
        
            public function setUp(): void
            {
                $this->pager = $this->prophesize(Pager::class);
        
                // Notice we pass the mocked `pager` object here
                $this->apiManager = new ApiManager(
                    $this->pager->reveal()
                );
            }
        
            public function testGetProjects(): void
            {
                // Given
                $projects = $this->givenTwoProjectsExist();
        
                $this->pager->getProjects(
                    'projects',
                    'all',
                    [['simple' => true]]
                )
                ->shouldBeCalledOnce()
                ->willReturn($projects);
        
                // When
                $result = $this->apiManager->fetchAll();
        
                // Then
                self::assertEquals($projects, $result);
            }
        }
        

        您可以在此处阅读有关“给定、何时、然后”结构的更多信息:https://thephp.website/en/issue/clean-tests-with-php-and-phpunit/

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-03-21
          • 1970-01-01
          • 1970-01-01
          • 2013-12-12
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多