如今,许多应用程序需要连接到外部资源以执行某些操作,例如发送电子邮件,从其他平台同步数据等。
在本文中,我将向您展示如何使用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 EM>变量具有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被模拟且没有效果。