在这个例子中,我们将测试一个外部API的模拟和不模拟Guzzle客户端。我们现在都知道,在测试环境中,外部API调用必须被模拟。然而,出于演示的目的,我将向你展示如果我们不模拟它,我们的测试会是什么样子。
真实的API响应
{
"status": 200,
"result": {
"postcode": "REAL POSTCODE",
"quality": 1,
"eastings": 1234,
"northings": 12345,
"country": "England",
"nhs_ha": "London",
"longitude": -0.123456789,
"latitude": 1.12345678,
"european_electoral_region": "London",
"primary_care_trust": "City",
"region": "London",
"lsoa": "City",
"msoa": "City 123",
"incode": "POSTCODE",
"outcode": "REAL",
"parliamentary_constituency": "City Central",
"admin_district": "City",
"parish": "City, unparished area",
"admin_county": null,
"admin_ward": "Westminister",
"ccg": "NHS City",
"nuts": "City",
"codes": {
"admin_district": "U1234567",
"admin_county": "U12345678",
"admin_ward": "U1234567",
"parish": "U123456",
"parliamentary_constituency": "U12345678",
"ccg": "U1234567",
"nuts": "UK1234"
}
}
}
异常
namespace Application\Exception;
use Exception;
class PostcodesException extends Exception
{
public function __construct($message, $code = 400)
{
parent::__construct($message, $code);
}
}
邮政编码
这个类调用外部API来获取英国的邮编信息,我们将测试它。
namespace Application\Util;
use Application\Exception\PostcodesException;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\Stream;
class Postcodes
{
private $client;
private $baseUri;
public function __construct(Client $client, $baseUri)
{
$this->client = $client;
$this->baseUri = $baseUri;
}
public function getData($postcode)
{
if (!$postcode) {
$this->throwException('Postcode is required.');
}
try {
/** @var Response $response */
$response = $this->client->request('GET', $this->baseUri.$postcode);
/** @var Stream $body */
$body = $response->getBody();
return $body->getContents();
} catch (ClientException $e) {
$this->throwException(sprintf('Failed to get postcode data for "%s".', $postcode));
}
}
private function throwException($message, $code = 400)
{
throw new PostcodesException($message, $code);
}
}
没有模拟
namespace tests\Application\Util;
use Application\Exception\PostcodesException;
use Application\Util\Postcodes;
use GuzzleHttp\Client;
use PHPUnit\Framework\TestCase;
class PostcodesTest extends TestCase
{
/** @var Postcodes */
private $postcodes;
protected function setUp()
{
$client = new Client();
$baseUri = 'http://api.postcodes.io/postcodes/';
$this->postcodes = new Postcodes($client, $baseUri);
}
protected function tearDown()
{
$this->postcodes = null;
}
public function testShouldThrowExceptionForEmptyPostcodeArgument()
{
$this->expectException(PostcodesException::class);
$this->expectExceptionMessage('Postcode is required.');
$this->expectExceptionCode(400);
$this->postcodes->getData('');
}
public function testShouldThrowExceptionForInvalidPostcodeArgument()
{
$postcode = 'INVALID';
$this->expectException(PostcodesException::class);
$this->expectExceptionMessage(sprintf('Failed to get postcode data for "%s".', $postcode));
$this->expectExceptionCode(400);
$this->postcodes->getData($postcode);
}
public function testShouldReturnPostcodeData()
{
$expected = file_get_contents(__DIR__.'/Mock/Postcodes/response-body.txt');
$result = $this->postcodes->getData('REAL POSTCODE');
$this->assertEquals($expected, $result);
}
}
#tests/Application/Util/Mock/Postcodes/response-body.txt
Content of this file is exactly the same as Real API response I added above.
测试
$ vendor/bin/phpunit --filter PostcodesTest tests/Application/Util/PostcodesTest.php
PHPUnit 5.7.22 by Sebastian Bergmann and contributors.
... 3 / 3 (100%)
Time: 97 ms, Memory: 4.25MB
OK (3 tests, 5 assertions)
有嘲讽
更多信息,请访问测试Guzzle客户端。
namespace tests\Application\Util;
use Application\Exception\PostcodesException;
use Application\Util\Postcodes;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use PHPUnit\Framework\TestCase;
class PostcodesTest extends TestCase
{
public function testShouldThrowExceptionForEmptyPostcodeArgument()
{
$this->expectException(PostcodesException::class);
$this->expectExceptionMessage('Postcode is required.');
$this->expectExceptionCode(400);
$postcodes = $this->getPostcodes(400);
$postcodes->getData('');
}
public function testShouldThrowExceptionForInvalidPostcodeArgument()
{
$postcode = 'INVALID';
$this->expectException(PostcodesException::class);
$this->expectExceptionMessage(sprintf('Failed to get postcode data for "%s".', $postcode));
$this->expectExceptionCode(400);
$postcodes = $this->getPostcodes(400);
$postcodes->getData($postcode);
}
public function testShouldReturnPostcodeData()
{
$body = file_get_contents(__DIR__.'/Mock/Postcodes/response-body.txt');
$postcodes = $this->getPostcodes(200, $body);
$result = $postcodes->getData('XYZ XYZ');
$this->assertEquals($body, $result);
}
private function getPostcodes($status, $body = null)
{
$mock = new MockHandler([new Response($status, [], $body)]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
return new Postcodes($client, 'http://mocked.postcodes.xyz/');
}
}
#tests/Application/Util/Mock/Postcodes/response-body.txt
{
"status": 200,
"result": {
"postcode": "XYZ XYZ",
"longitude": -0.000000,
"latitude": 1.111111
}
}
测试
$ vendor/bin/phpunit --filter PostcodesTest tests/Application/Util/PostcodesTest.php
PHPUnit 5.7.22 by Sebastian Bergmann and contributors.
... 3 / 3 (100%)
Time: 97 ms, Memory: 4.25MB
OK (3 tests, 5 assertions)