使用phpunit测试外部API
#php #api #测试 #phpunit

如今,许多应用程序需要连接到外部资源以执行某些操作,例如发送电子邮件,从其他平台同步数据等。
在本文中,我将向您展示如何使用phpunit test doubles来测试API而无需连接。如果API不可用,这将确保您的测试不会失败。

我们将使用climate open api作为外部API和Symfony http client组件作为客户的客户

如果您向此URL提出http请求:

https://climate-api.open-meteo.com/v1/climate?latitude=40.4165&longitude=-3.7026&start_date=2023-07-09&end_date=2023-07-11&models=CMCC_CM2_VHR4&daily=temperature_2m_max

您将获得以下JSON数据作为响应:

{
  "latitude": 40.40001,
  "longitude": -3.699997,
  "generationtime_ms": 0.20503997802734375,
  "utc_offset_seconds": 0,
  "timezone": "GMT",
  "timezone_abbreviation": "GMT",
  "elevation": 651,
  "daily_units": {
    "time": "iso8601",
    "temperature_2m_max": "°C"
  },
  "daily": {
    "time": [
      "2023-07-09",
      "2023-07-10",
      "2023-07-11"
    ],
    "temperature_2m_max": [
      34.8,
      35.1,
      33.8
    ]
  }
}

在以下各节中,我们将将测试集中在确保设置 daily 键上,并包含 time deverip_2m_max data。

让我们开始。

测试的代码

use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

class ClimateHandler
{
    public function __construct(
        private readonly HttpClientInterface $httpClient
    ){ }


    public function getClimateData(string $latitude, string $longitude, \DateTimeImmutable $from, \DateTimeImmutable $to): array
    {
        try{
            $response = $this->httpClient->request('GET', 'https://climate-api.open-meteo.com/v1/climate', [
                'query' => [
                    'latitude'   => $latitude,
                    'longitude'  => $longitude,
                    'start_date' => $from->format('Y-m-d'),
                    'end_date'   => $to->format('Y-m-d'),
                ]
            ]);

            return ['status_code' => $response->getStatusCode(), 'data' => $response->toArray()];
        }
        catch (HttpExceptionInterface $exception)
        {
            return ['status_code' => $exception->getResponse()->getStatusCode(), 'data' => $exception->getResponse()->toArray(false)];
        }
    }
}

如上所述, climateHandler 使http获取请求以气候API,并返回带有响应数据和状态代码的数组。如果发生错误OCURR,它将返回状态错误代码和响应错误(在异常对象上保留)。

模拟外部API

以下Phpunit类有两个测试。一个人通过正确的气候数据返回A 200 OK 响应,另一个返回a 400不良请求由于某些错误。

use App\Application\ClimateHandler;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;

class ClimateHandlerTest extends TestCase
{
    public function testClimateHandler()
    {
        $mockResponse = <<<JSON
{
  "daily": {
    "time": [
      "2023-07-09",
      "2023-07-10",
      "2023-07-11"
    ],
    "temperature_2m_max": [
      34.8,
      35.1,
      33.8
    ]
  }
}
JSON;

        $httpClient = new MockHttpClient([
            new MockResponse($mockResponse, ['http_code' => 200, 'response_headers' => ['Content-Type: application/json']])
        ]);

        $stub = $this
            ->getMockBuilder(ClimateHandler::class)
            ->setConstructorArgs([$httpClient])
            ->onlyMethods([])
            ->getMock()
        ;

        $response = $stub->getClimateData(40.416, -3.7026, new \DateTimeImmutable('2023-07-09 00:00:00'), new \DateTimeImmutable('2023-07-12 00:00:00'));
        $this->assertEquals(200, $response['status_code']);
        $this->assertCount(3, $response['data']['daily']['time']);
        $this->assertCount(3, $response['data']['daily']['temperature_2m_max']);
    }

    public function testClimateHandlerError()
    {
        $mockResponse = <<<JSON
{"reason":"Value of type 'String' required for key 'end_date'.","error":true}
JSON;

        $httpClient = new MockHttpClient([
            new MockResponse($mockResponse, ['http_code' => 400, 'response_headers' => ['Content-Type: application/json']])
        ]);

        $stub = $this
            ->getMockBuilder(ClimateHandler::class)
            ->setConstructorArgs([$httpClient])
            ->onlyMethods([])
            ->getMock()
        ;

        $response = $stub->getClimateData(40.416, -3.7026, new \DateTimeImmutable('2023-07-09 00:00:00'), new \DateTimeImmutable('2023-07-12 00:00:00'));
        $this->assertEquals(400, $response['status_code']);
        $this->assertTrue($response['data']['error']);
    }
}

让我们探索最重要的部分:

$httpClient = new MockHttpClient([
    new MockResponse($mockResponse, ['http_code' => 200, 'response_headers' => ['Content-Type: application/json']])
]);

在这里,我们创建一个 mockhttpclient (一个嘲笑组件提供的HTTP客户端的类。了解更多here),我们指示它返回 $ ockerresponse上的JSON包含的JSON 变量具有200个OK响应代码。我们还指出响应是JSON编码设置 content-type 标题。

$stub = $this
     ->getMockBuilder(ClimateHandler::class)
     ->setConstructorArgs([$httpClient])
     ->onlyMethods([])
     ->getMock()
;

现在,我们创建存根设置我们的 mockhttpclient 作为构造函数的第一个参数。我们还使用唯一methods 方法告诉存根,必须模拟任何方法(我们希望执行原始方法,因为我们将模拟的HTTP客户端传递给了构造函数),并使用 getmock

$response = $stub->getClimateData(40.416, -3.7026, new \DateTimeImmutable('2023-07-09 00:00:00'), new \DateTimeImmutable('2023-07-12 00:00:00'));
$this->assertEquals(200, $response['status_code']);
$this->assertCount(3, $response['data']['daily']['time']);
$this->assertCount(3, $response['data']['daily']['temperature_2m_max']);

最后,我们调用 getClimatedata ,并确保做下一个断言是有意义的:

  • status_code 包含200
  • 每日时间数组有3个日期
  • 每日温度_2m_max 阵列有3个度量

另一个测试, test ClimateHandlerError ,如 test callmateHandler 确实购买了其模拟返回a 400 badrequest http code http代码,而JSON内容则不同。如果我们看一下它的断言,我们可以看到以下内容:

$response = $stub->getClimateData(40.416, -3.7026, new \DateTimeImmutable('2023-07-09 00:00:00'), new \DateTimeImmutable('2023-07-12 00:00:00'));
$this->assertEquals(400, $response['status_code']);
$this->assertTrue($response['data']['error']);

在这种情况下,它检查了status_code是400,并且错误键为true

重要的是要注意,无论我们传递到 getClimatedata ,都有httpclient被模拟且没有效果。