cspray / http-client-test-interceptor
Requires
- php: ^8.1
- ext-dom: *
- ext-libxml: *
- amphp/amp: ^v3.0
- amphp/http: ^v2.0
- amphp/http-client: ^v5.0
- league/uri: ^7.4
- league/uri-components: ^7.4
- ramsey/uuid: ^4.3
- sebastian/diff: ^4.0 | ^5.0
Requires (Dev)
- mikey179/vfsstream: ^1.6
- phpunit/phpunit: ^10.0
- roave/security-advisories: dev-latest
This package is auto-updated.
Last update: 2024-09-04 13:35:18 UTC
README
这是一个用于测试使用它引入的应用拦截器设计模式的 amphp/http-client v5 框架。使用此库的主要原因是您的单元测试或集成测试中测试已知的 HTTP 交互。此库提供了 2 种测试这些交互的机制
- 编写 单元测试,其中每个请求都应该是模拟的,并且不应进行任何网络请求。
- 编写 集成测试,如果未存储该请求的固定文件,则请求将通过网络发送。从网络请求收到的响应将被存储,后续请求不会通过网络发送。
无论您编写单元测试还是集成测试,都使用相同的底层机制。您可以指定请求的细粒度部分,以便与它们进行匹配,或提供自己的匹配请求与固定文件的策略。
有许多不使用此库的理由
- 您没有使用 amphp/http-client v5 或更高版本。
- 您已经有了测试 API 交互的可行、有效的解决方案。
安装
composer require amphp/http-client:v5.0.0-beta.3
composer require --dev cspray/http-client-test-interceptor
amphp/http-client 库仍在测试版。您可能需要调整您的最低稳定性,直到 http-client 发布 5.0 版本。
单元测试快速入门
单元测试旨在测试您的 HttpClient 对特定、已知的 HTTP 交互的响应。例如,您可能需要测试您的测试代码对不同头部、特定错误代码或精确的正文结构的响应。这些测试通常很难进行,甚至可能无法通过集成测试可靠地进行。
与编写使用固定文件的集成测试不同,没有必要的设置步骤。第一步是编写您的 TestCase!
<?php declare(strict_types=1); namespace Acme\HttpMockingDemo; use Amp\Http\Client\HttpClientBuilder;use Amp\Http\Client\Request;use Cspray\HttpClientTestInterceptor\HttpMock\MockResponse;use Cspray\HttpClientTestInterceptor\HttpMockAwareTestTrait;use League\Uri\Http;use PHPUnit\Framework\TestCase; final class ApiUnitTest extends TestCase { use HttpMockAwareTestTrait; public function testGetResource() : void { $request = new Request(Http::createFromString('http://example.com'), 'POST'); $response = MockResponse::fromBody('My expected body'); $this->httpMock()->whenClientReceivesRequest($request)->willReturnResponse($response); $client = (new HttpClientBuilder())->intercept($this->getMockingInterceptor())->build(); // We match on the values of the mocked Request ... NOT the identity of the object $actual = $client->request(new Request(Http::createFromString('http://example.com')), 'POST'); // If you were to uncomment the below line this test would fail with a RequestNotMocked exception as the methods do not match // $client->request(new Request(Http::createFromString('http://example.com')), 'GET'); self::assertSame($response, $actual); } }
模拟请求匹配
由于模拟需要更多的手动设置,因此它们匹配的方式使用一组更宽松的请求匹配策略;而不是检查整个请求,只需将方法和 URI 匹配即可。如果您想使这更严格,可以在定义要模拟的请求时提供一组匹配器。
<?php declare(strict_types=1); namespace Acme\HttpMockingDemo; use Amp\Http\Client\HttpClientBuilder;use Amp\Http\Client\Request;use Cspray\HttpClientTestInterceptor\HttpMock\MockResponse;use Cspray\HttpClientTestInterceptor\HttpMockAwareTestTrait;use Cspray\HttpClientTestInterceptor\Matcher\Matcher;use League\Uri\Http;use PHPUnit\Framework\TestCase; final class ApiHeadersMatchingUnitTest extends TestCase { use HttpMockAwareTestTrait; public function testGetResource() : void { $request = new Request(Http::createFromString('http://example.com'), 'POST'); $request->setHeader('Authorization', 'some-token'); $response = MockResponse::fromBody('My expected body'); $this->httpMock()->whenClientReceivesRequest($request, [Matcher::Uri, Matcher::Method, Matcher::Headers]) ->willReturnResponse($response); $client = (new HttpClientBuilder())->intercept($this->getMockingInterceptor())->build(); // We match on the values of the mocked Request ... NOT the identity of the object $actualRequest = new Request('http://example.com', 'POST'); $actualRequest->setHeader('Authorization', 'some-token'); $actual = $client->request($actualRequest); // If you were to uncomment the below line this test would fail with a RequestNotMocked exception as the _headers_ do not match // $client->request(new Request(Http::createFromString('http://example.com')), 'POST'); self::assertSame($response, $actual); } }
集成测试快速入门
集成测试旨在测试您的 HttpClient 对 实际 API 的真实响应。当请求第一次通过网络发送时,会存储一个固定文件与响应。如果再次发送匹配的请求,将根据存储的固定文件生成响应,而不是通过网络发送。
创建固定文件目录
首先需要做的是创建一个存储 HTTP 固定文件的目录。没有“正确”的存储这些文件的位置,任何可写目录都适用。假设您的测试位于名为 test
的目录中,本指南建议创建一个 test/http_fixture
目录。
mkdir -p test/http_fixture
标记发送 HTTP 请求的测试
现在,在您的测试中,将 TestCase
或测试方法标记为存储和用于匹配请求的固定文件路径。
<?php declare(strict_types=1); // Stored in test/ApiTest.php namespace Acme\HttpFixtureDemo; use Amp\Http\Client\HttpClientBuilder; use Amp\Http\Client\Request; use Cspray\HttpClientTestInterceptor\Attribute\HttpFixture; use Cspray\HttpClientTestInterceptor\HttpFixtureAwareTestTrait; use PHPUnit\Framework\TestCase; #[HttpFixture(__DIR__ . '/http_fixture')] final class ApiTest extends TestCase { use HttpFixtureAwareTestTrait; public function testGetResource() : void { $httpClient = (new HttpClientBuilder()) ->intercept($this->getTestInterceptor()) ->build(); $response = $httpClient->request(new Request('https://api.example.com')); self::assertSame(200, $response->getStatus()); } }
集成请求匹配
默认情况下,库将尝试将请求的每个方面与集成测试中存储的固定文件进行匹配。可以精确控制哪些请求部分进行匹配。例如,让我们设置我们的 TestInterceptor
,只匹配请求方法和 URI,而不是所有内容。
<?php declare(strict_types=1); // Stored in test/ApiTest.php namespace Acme\HttpFixtureDemo; use Amp\Http\Client\HttpClientBuilder;use Amp\Http\Client\Request;use Cspray\HttpClientTestInterceptor\Attribute\HttpFixture;use Cspray\HttpClientTestInterceptor\Attribute\HttpRequestMatchers;use Cspray\HttpClientTestInterceptor\HttpFixtureAwareTestTrait;use Cspray\HttpClientTestInterceptor\Matcher\Matcher;use PHPUnit\Framework\TestCase; #[HttpFixture(__DIR__ . '/http_fixture')] #[HttpRequestMatchers(Matcher::Method, Matcher::Uri)] final class ApiTest extends TestCase { use HttpFixtureAwareTestTrait; public function testGetResource() : void { $httpClient = (new HttpClientBuilder()) ->intercept($this->getTestInterceptor()) ->build(); $response = $httpClient->request(new Request('https://api.example.com')); self::assertSame(200, $response->getStatus()); } }
现在使用的 RequestMatchingStrategy
仅检查方法和 URI。您可以根据请求的适当部分来组合您的请求匹配。还可以在单独的测试方法上定义 #[HttpRequestMatchers]
和 #[HttpFixture]
属性。如果您在测试方法上提供了属性,则这些属性将覆盖 TestCase 上的属性。