macpaw / extended_mock_http_client
该仓库提供Symfony HTTP Client的mock,比默认的Symfony mock更灵活。
v4.0.1
2024-01-15 15:51 UTC
Requires
- php: >=8.1
- ext-json: *
- symfony/cache: ^4.4 || ^5.0 || ^6.0 || ^7.0
- symfony/config: ^4.4 || ^5.0 || ^6.0 || ^7.0
- symfony/dependency-injection: ^4.4 || ^5.0 || ^6.0 || ^7.0
- symfony/framework-bundle: ^4.4 || ^5.0 || ^6.0 || ^7.0
- symfony/http-client: ^4.4 || ^5.0 || ^6.0 || ^7.0
- symfony/http-foundation: ^4.4 || ^5.0 || ^6.0 || ^7.0
- symfony/http-kernel: ^4.4 || ^5.0 || ^6.0 || ^7.0
- symfony/yaml: ^4.4 || ^5.0 || ^6.0 || ^7.0
Requires (Dev)
- phpstan/extension-installer: ^1.1
- phpstan/phpstan: ^1.5.3
- phpstan/phpstan-deprecation-rules: ^1.0
- phpstan/phpstan-phpunit: ^1.1
- phpstan/phpstan-strict-rules: ^1.1
- phpstan/phpstan-symfony: ^1.1
- phpunit/phpunit: ^8.5 || 9.5
- squizlabs/php_codesniffer: ^3.5
README
安装
composer require macpaw/extended_mock_http_client
如何使用
在配置文件 config/services_test.yaml
中替换当前的HTTP客户端服务
imports: - { resource: services.yaml } services: _defaults: autowire: true autoconfigure: true public: true http_client_service_name: class: ExtendedMockHttpClient\ExtendedMockHttpClient arguments: - 'https://test.host'
这就完成了,你可以在PHPUnit测试中使用它。
示例
简单示例
abstract class AbstractFunctionalTest extends KernelTestCase { private ExtendedMockHttpClient $httpClient protected function setUp(): void { /** @var ExtendedMockHttpClient $mockHttpClient */ $this->httpClient = self::getContainer()->get('http_client_service_name'); } } class MyTest extends AbstractFunctionalTest { /** * Create simple request using createFixture * Request with almost empty parameters * Check response and check called times */ public function testSimpleExample1(): void { $httpFixture = $this->client->createFixture( 'POST', 'https://test.test/foo?foo=bar', null, null, 200, 'ok' ); $this->client->addFixture($httpFixture); $response = $this->client->request('POST', 'https://test.test/foo?foo=bar'); self::assertEquals(200, $response->getStatusCode()); self::assertEquals('ok', $response->getContent()); self::assertEquals(1, $httpFixture->getCalledTimes()); } /** * Make simple fixture using createFixture * Request using json * Check response */ public function testSimpleExample2(): void { $httpFixture = $this->client->createFixture( 'POST', 'https://test.test/foo?foo=bar', '{"foo":"bar","baz":123}', [ 'x-header' => 'x-value', ], 200, 'ok' ); $this->client->addFixture($httpFixture); $response = $this->client->request('POST', 'https://test.test/foo?foo=bar', [ 'json' => [ 'foo' => 'bar', 'baz' => 123 ], 'headers' => [ 'x-header' => 'x-value', ] ]); self::assertEquals(200, $response->getStatusCode()); self::assertEquals('ok', $response->getContent()); } }
使用构建器示例
class MyTest extends AbstractFunctionalTest { /** * Make fixture using builder * Request using json * Check response */ public function testBuilderExample1(): void { $builder = $this->client->getHttpFixtureBuilder(); $httpFixture = $builder ->request( $builder->method(['PUT', 'POST']), $builder->url('https://test.test/foo'), $builder->query([ 'foo' => 'bar', ]), $builder->body($builder->jsonToArray( $builder->arrayContain([ 'foo' => 'bar', ]) )), $builder->headers([ 'x-header' => 'x-value', ]) ) ->response(200, 'ok') ->build(); $this->client->addFixture($httpFixture); $response = $this->client->request('POST', 'https://test.test/foo?foo=bar', [ 'json' => [ 'foo' => 'bar', 'baz' => 123 ], 'headers' => [ 'x-header' => 'x-value', ] ]); self::assertEquals(200, $response->getStatusCode()); self::assertEquals('ok', $response->getContent()); } /** * Make fixture using builder with MockResponse * Request using json * Check response */ public function testBuilderExample2(): void { $builder = $this->client->getHttpFixtureBuilder(); $httpFixture = $builder ->request( $builder->method('POST'), $builder->url('https://test.test/foo'), $builder->query($builder->queryToArray($builder->arrayContain([ 'foo' => 'bar', ]))), $builder->body($builder->stringRegex('/"foo":"bar"/')), $builder->headers([ 'x-header' => 'x-value', ]) ) ->response(new MockResponse('ok', ['http_code' => 200])) ->build(); $this->client->addFixture($httpFixture); $response = $this->client->request('POST', 'https://test.test/foo?foo=bar', [ 'json' => [ 'foo' => 'bar', 'baz' => 123 ], 'headers' => [ 'x-header' => 'x-value', ] ]); self::assertEquals(200, $response->getStatusCode()); self::assertEquals('ok', $response->getContent()); } }
在请求和响应中使用回调示例
class MyTest extends AbstractFunctionalTest { /** * Make fixture using builder with callbacks in request and response * Request using json * Check response */ public function testCallbackExample(): void { $builder = $this->client->getHttpFixtureBuilder(); $httpFixture = $builder ->request( $builder->method($builder->callback(function (string $method): bool { return $method === 'POST'; })), $builder->url($builder->callback(function (string $url): bool { return $url === 'https://test.test/foo'; })), $builder->query( $builder->callback(function (string $query): bool { return $query === 'foo=bar'; }), $builder->queryToArray( $builder->callback(function (array $arrayQuery): bool { return array_key_exists('foo', $arrayQuery); }) ) ), $builder->body($builder->callback(function (string $jsonBody): bool { $arrayBody = json_decode($jsonBody, true); return isset($arrayBody['foo']); })), $builder->headers($builder->callback(function (array $headers): bool { return array_key_exists('x-header', $headers); })) ) ->response( function (string $method, string $url, string $query, string $body, array $headers): MockResponse { $stringHeaders = []; foreach ($headers as $key => $value) { $stringHeaders[] = "$key: $value"; } return new MockResponse(json_encode([ 'method' => $method, 'url' => $url, 'query' => $query, 'body' => $body, 'headers' => $headers, ])); } ) ->build(); $this->client->addFixture($httpFixture); $response = $this->client->request('POST', 'https://test.test/foo?foo=bar', [ 'json' => [ 'foo' => 'bar', 'baz' => 123 ], 'headers' => [ 'x-header' => 'x-value', ] ]); self::assertEquals(200, $response->getStatusCode()); $responseArray = json_decode($response->getContent(), true); self::assertEquals('POST', $responseArray['method']); self::assertEquals('https://test.test/foo', $responseArray['url']); self::assertEquals('foo=bar', $responseArray['query']); self::assertEquals('{"foo":"bar","baz":123}', $responseArray['body']); self::assertArrayHasKey('x-header', $responseArray['headers']); } }
如何注册自定义比较器
创建比较器类,它应该实现 ComparatorInterface
use ExtendedMockHttpClient\HttpFixture\Request\Comparator\ComparatorInterface; class CustomComparator implements ComparatorInterface { /** * @var string */ private $stringPart1; /** * @var string */ private $stringPart2; public static function getName(): string { return 'custom'; } public function __construct(string $stringPart1, string $stringPart2) { $this->stringPart1 = $stringPart1; $this->stringPart2 = $stringPart2; } public function __invoke($value): bool { return $value === "$this->stringPart1.$this->stringPart2"; } }
重写 HttpFixtureFactory
以添加新比较器的使用位置
services: ExtendedMockHttpClient\Factory\HttpFixtureFactory: arguments: - '%allowed_nested_keys%' calls: - add: ['body', 'custom'] - add: ['method', 'custom'] - add: ['query', 'custom'] ...
在测试中使用新比较器
class MyTest extends AbstractFunctionalTest { /** * Make fixture using builder with custom comparator * Request using string body * Check response */ public function testCustomComparator(): void { $builder = $this->client->getHttpFixtureBuilder(); $httpFixture = $builder ->request( $builder->body(new CustomComparator('foo', 'bar')) ) ->response(200, 'ok') ->build(); $this->client->addFixture($httpFixture); $response = $this->client->request('GET', 'https://test.test', [ 'body' => 'foo.bar' ]); self::assertEquals(200, $response->getStatusCode()); self::assertEquals('ok', $response->getContent()); } }
如何重写HttpFixtureBuilderFactory以使用更有用的构建器方法
创建基于原始构建器的自定义构建器类
use ExtendedMockHttpClient\Builder\HttpFixtureBuilder as BaseHttpFixtureBuilder; use ExtendedMockHttpClient\Tests\Fixture\Application\HttpFixture\Request\Comparator\CustomComparator; class HttpFixtureBuilder extends BaseHttpFixtureBuilder { public function custom(string $stringPart1, string $stringPart2): CustomComparator { return new CustomComparator($stringPart1, $stringPart2); } }
创建基于原始构建器工厂的自定义构建器工厂类
use ExtendedMockHttpClient\Factory\HttpFixtureBuilderFactory as BaseHttpFixtureBuilderFactory; use ExtendedMockHttpClient\Builder\HttpFixtureBuilder as BaseHttpFixtureBuilder; use ExtendedMockHttpClient\Tests\Fixture\Application\Builder\HttpFixtureBuilder; class HttpFixtureBuilderFactory extends BaseHttpFixtureBuilderFactory { public function create(): BaseHttpFixtureBuilder { return new HttpFixtureBuilder($this->httpFixtureFactory); } }
重写构建器工厂服务
services: ExtendedMockHttpClient\Factory\HttpFixtureBuilderFactory: class: ExtendedMockHttpClient\Tests\Fixture\Application\Factory\HttpFixtureBuilderFactory
在测试中使用更新的构建器
class MyTest extends AbstractFunctionalTest { /** * Make fixture using overwrote builder with custom comparator * Request using string body * Check response */ public function testBuilderOverwrote(): void { /** @var HttpFixtureBuilder $builder */ $builder = $this->client->getHttpFixtureBuilder(); $httpFixture = $builder ->request( $builder->body($builder->custom('foo', 'bar')) ) ->response(200, 'ok') ->build(); $this->client->addFixture($httpFixture); $response = $this->client->request('GET', 'https://test.test', [ 'body' => 'foo.bar' ]); self::assertEquals(200, $response->getStatusCode()); self::assertEquals('ok', $response->getContent()); } }
待办事项列表
- 添加对jms序列化的支持
- 添加历史功能
- 获取最后一个请求/响应(或按索引)
- 某种断言,它应该检查历史中是否包含某些请求
- 添加从数组/yaml加载固件的可能性
- 添加记录器并记录每个步骤以简化调试