waylandace / pact-php
启用消费者驱动的合约测试,遵循PACT基础原则。
Requires
- php: ^7.3|^8.0
- ext-json: *
- ext-openssl: *
- amphp/amp: ^2.5.1
- amphp/byte-stream: ^1.8
- amphp/cache: v1.4.0
- amphp/dns: ^1.2.3
- amphp/hpack: ^3.1.0
- amphp/http-server: ^2.1
- amphp/log: ^1.1
- amphp/process: ^1.1.1
- amphp/serialization: ^1.0
- amphp/socket: ^1.1.3
- amphp/sync: ^1.4.0
- amphp/windows-registry: v0.3.3
- composer/semver: ^1.4.0|^3.2.0
- guzzlehttp/guzzle: ^6.5|^7.2.0
- phpunit/phpunit: >=8.2.3
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- mockery/mockery: ^1.4.2
- php-amqplib/php-amqplib: ^3.0
- phpstan/phpstan: ^0.12.90
- roave/security-advisories: dev-latest
- slim/psr7: ^1.2.0
- slim/slim: ^4.6
- dev-master
- 7.2.5
- 7.2.4
- 7.2.3
- 7.2.2
- 7.2.1
- 7.2.0
- 7.0.1
- 7.0.0
- 6.0.3
- 6.0.2
- 6.0.1
- 6.0.0
- 5.0.7
- 5.0.6
- 5.0.5
- 5.0.4
- 5.0.3
- 5.0.2
- 5.0.1
- 5.0.0
- 4.0.2
- 4.0.1
- 4.0.0
- 3.1.1
- 3.1.0
- 3.0.3
- 3.0.2
- 3.0.1
- 3.0.0
- 2.2.1
- 2.2.0
- 2.1.0
- 2.0.4
- 2.0.3
- 2.0.2
- 2.0.1
- 1.1.3
- 1.1.0
- 1.0.5
- 1.0.4
- 1.0.3
- 1.0.1
- dev-chore/phpstan-level7
- dev-fix/display_stderr_on_publish_error
- dev-feat/fetch_from_broker
- dev-chore/pact-publish-standalone
- dev-feat/branch-pact-verify
- dev-feat/publish-contract
- dev-fix/guzzle6
- dev-bugfix/reduce-semver-ver
- dev-ubuntu-sockets
- dev-githubactions
- dev-update-amp
- dev-chore/upgrade-to-pact-ruby-standalone-1-86-0
- dev-fix-pact-json-examples
- dev-uprev-pact
- dev-update-example-json
- dev-new-logger-refactor
This package is auto-updated.
Last update: 2024-08-30 01:44:55 UTC
README
Pact的PHP版本。Pact启用消费者驱动的合约测试。请阅读Pact.io以获取有关PACT的详细信息。
目录
版本
8.X更新了内部依赖和库。这导致不支持PHP 7.3。
7.x更新了内部依赖和库,主要是Guzzle 7.X。这导致不支持PHP 7.2。6.x更新了内部依赖,主要是围绕Amp库。这导致不支持PHP 7.1。
5.X添加了对异步消息和Pact规范3.X的初步支持。由于后端实现不完整,因此尚不支持完整的Pact规范3.X。但是,支持pact-messages。
4.X标签是PHPUnit 7.X更改的伴随,它需要PHP 7.1或更高版本。因此,4.X不支持PHP 7.0。
3.X标签是对2.X版本的重大变更。为了与Pact生态系统中的其他部分保持一致,Pact-PHP迁移到利用Ruby后端。这反映了.NET、JS、Python和Go实现。
如果您想继续使用2.X实现,可以继续从最新的2.X.X标签中提取。
规范
3.X版本是Pact-PHP的版本,而不是它支持的Pact规范版本。Pact-Php 3.X支持Pact-Specification 2.X。
安装
使用以下命令安装最新版本:
$ composer require pact-foundation/pact-php --dev
Composer将旧版本托管在mattersight/phppact下,该版本已弃用。请将其转换为新的包名。
基本消费者使用
以下所有代码都仅用于消费者。
启动和停止模拟服务器
此库包含对Ruby独立模拟服务的包装。
配置此内容的最佳方法是使用PHPUnit监听器。此项目包含一个默认监听器,请参阅PactTestListener.php。这利用环境变量进行配置。这些环境变量可以添加到系统中,也可以添加到phpunit.xml配置文件中。以下是一个配置为使用默认设置的phpunit.xml文件示例。请注意,测试套件和参数数组必须具有相同的值。
或者,您可以根据以下示例以您喜欢的任何方式启动和停止
<?php use PhpPact\Standalone\MockService\MockServer; use PhpPact\Standalone\MockService\MockServerConfig; // Create your basic configuration. The host and port will need to match // whatever your Http Service will be using to access the providers data. $config = new MockServerConfig(); $config->setHost('localhost'); $config->setPort(7200); $config->setConsumer('someConsumer'); $config->setProvider('someProvider'); $config->setCors(true); // Instantiate the mock server object with the config. This can be any // instance of MockServerConfigInterface. $server = new MockServer($config); // Create the process. $server->start(); // Stop the process. $server->stop();
创建消费者单元测试
创建一个标准的PHPUnit测试用例类和函数。
点击此处查看完整示例文件。
创建模拟请求
这将定义从您的http服务发送的预期请求的形状。
$request = new ConsumerRequest(); $request ->setMethod('GET') ->setPath('/hello/Bob') ->addHeader('Content-Type', 'application/json');
您也可以创建一个与提供者示例中看到的一样地体。
创建模拟响应
这将定义提供者响应的形状。
$matcher = new Matcher(); $response = new ProviderResponse(); $response ->setStatus(200) ->addHeader('Content-Type', 'application/json') ->setBody([ 'message' => $matcher->regex('Hello, Bob', '(Hello, )[A-Za-z]') ]);
在这个示例中,我们使用匹配器。这允许我们在匹配期望值与实际值时添加灵活的规则。在示例中,您将看到使用正则表达式来验证响应的有效性。
$matcher = new Matcher(); $response = new ProviderResponse(); $response ->setStatus(200) ->addHeader('Content-Type', 'application/json') ->setBody([ 'list' => $matcher->eachLike([ 'firstName' => 'Bob', 'age' => 22 ]) ]);
构建交互
现在我们有了请求和响应,我们需要构建交互并将其发送到模拟服务器。
// Create a configuration that reflects the server that was started. You can // create a custom MockServerConfigInterface if needed. This configuration // is the same that is used via the PactTestListener and uses environment variables. $config = new MockServerEnvConfig(); $builder = new InteractionBuilder($config); $builder ->given('a person exists') ->uponReceiving('a get request to /hello/{name}') ->with($request) ->willRespondWith($response); // This has to be last. This is what makes an API request to the Mock Server to set the interaction.
发送请求
$service = new HttpClientService($config->getBaseUri()); // Pass in the URL to the Mock Server. $result = $service->getHelloString('Bob'); // Make the real API request against the Mock Server.
验证交互
验证所有已注册的交互是否都已发生。这通常应该在每个测试中执行,以便正确标记未通过验证的测试。
$builder->verify();
进行断言
验证根据配置的响应预期数据是否正确。
$this->assertEquals('Hello, Bob', $result); // Make your assertions.
基本提供者使用
以下所有代码仅用于提供者。这将运行Pacts与真实提供者进行比较,并在Pact Broker上验证或失败验证。
创建单元测试
创建一个单独的单元测试函数。这将测试该服务的单个消费者。
启动API
启动API实例。有关提示,请点击此处。
如果需要在发送每个请求之前设置API的状态,请参阅设置提供者状态。
提供者验证
有三种方式验证Pact文件。请参阅下面的示例。
从Pact代理验证
这将从Pact Broker获取Pact文件并运行数据与已启动的API进行比较。
$config = new VerifierConfig(); $config ->setProviderName('someProvider') // Providers name to fetch. ->setProviderVersion('1.0.0') // Providers version. ->setProviderBranch('main') // Providers git branch name. ->setProviderBaseUrl(new Uri('https://:58000')) // URL of the Provider. ->setBrokerUri(new Uri('https://')) // URL of the Pact Broker to publish results. ->setPublishResults(true) // Flag the verifier service to publish the results to the Pact Broker. ->setProcessTimeout(60) // Set process timeout (optional) - default 60 ->setProcessIdleTimeout(10) // Set process idle timeout (optional) - default 10 ->setEnablePending(true) // Flag to enable pending pacts feature (check pact docs for further info) ->setIncludeWipPactSince('2020-01-30') //Start date of WIP Pacts (check pact docs for further info) ->setRequestFilter( function (RequestInterface $r) { return $r->withHeader('MY_SPECIAL_HEADER', 'my special value'); } ); // Verify that the Consumer 'someConsumer' that is tagged with 'master' is valid. $verifier = new Verifier($config); $verifier->verify('someConsumer', 'master'); // The tag is option. If no tag is set it will just grab the latest. // This will not be reached if the PACT verifier throws an error, otherwise it was successful. $this->assertTrue(true, 'Pact Verification has failed.');
从Pact代理验证所有
这将获取与给定提供者关联的所有Pact文件。
public function testPactVerifyAll() { $config = new VerifierConfig(); $config ->setProviderName('someProvider') // Providers name to fetch. ->setProviderVersion('1.0.0') // Providers version. ->setProviderBranch('main') // Providers git branch name. ->setProviderBaseUrl(new Uri('https://:58000')) // URL of the Provider. ->setBrokerUri(new Uri('https://')) // URL of the Pact Broker to publish results. ->setPublishResults(true) // Flag the verifier service to publish the results to the Pact Broker. ->setEnablePending(true) // Flag to enable pending pacts feature (check pact docs for further info) ->setIncludeWipPactSince('2020-01-30') //Start date of WIP Pacts (check pact docs for further info) // Verify that all consumers of 'someProvider' are valid. $verifier = new Verifier($config); $verifier->verifyAll(); // This will not be reached if the PACT verifier throws an error, otherwise it was successful. $this->assertTrue(true, 'Pact Verification has failed.'); }
通过路径验证文件
这允许本地Pact文件测试。
public function testPactVerifyAll() { $config = new VerifierConfig(); $config ->setProviderName('someProvider') // Providers name to fetch. ->setProviderVersion('1.0.0') // Providers version. ->setProviderBranch('main') // Providers git branch name. ->setProviderBaseUrl(new Uri('https://:58000')) // URL of the Provider. ->setBrokerUri(new Uri('https://')) // URL of the Pact Broker to publish results. ->setPublishResults(true); // Flag the verifier service to publish the results to the Pact Broker. ->setEnablePending(true) // Flag to enable pending pacts feature (check pact docs for further info) ->setIncludeWipPactSince('2020-01-30') //Start date of WIP Pacts (check pact docs for further info) // Verify that the files in the array are valid. $verifier = new Verifier($config); $verifier->verifyFiles(['C:\SomePath\consumer-provider.json']); // This will not be reached if the PACT verifier throws an error, otherwise it was successful. $this->assertTrue(true, 'Pact Verification has failed.'); }
技巧
异步启动API
您可以在测试的setUp函数期间使用内置的PHP服务器来完成此操作。可以使用Symfony Process库异步运行进程。
设置提供者状态
PACT验证器是Ruby独立验证器的包装器。有关如何工作的更多信息,请参阅带有提供者状态的API。由于大多数PHP REST API是无状态的,这需要一些思考。
这里有几种选择
- 将发布的state写入文件,并使用工厂根据state决定使用哪个mock repository类。
- 设置数据库以满足请求的预期。在每个请求的开始,您应该首先将数据库重置到其原始状态。
无论您选择哪种方式,您都必须修改PHP进程之外的内容,因为每个对您服务器的请求都将是无状态的且独立。
其他示例
有一个独立的存储库,包含2.X和3.X实现的端到端示例。
- pact-php-example用于3.X示例
- 2.2.1标签用于2.X示例
消息支持
这个特性是初步的,因为整个 Pact 社区正在完善它。目标不是测试对象在总线上的传输,而是审查消息的内容。虽然示例侧重于 Rabbit MQ,但确切的消息队列无关紧要。初始比较需要消息的发布者/生产者和消费者创建一定类型的对象。这包括一个元数据集,您可以存储发布者和消费者同意的键、队列、交换等。内容格式必须是 JSON。
为了利用现有的 pact-verification 工具,等式的一侧设置一个 http 代理来回调处理类。除了更改默认端口外,这应该对库用户来说是透明的。
提供者和消费者双方都大量使用 lambda 函数。
消费者端消息处理
提供的示例相当基础。请参阅 examples\tests\MessageConsumer。
- 创建内容和元数据(数组)
- 为 MessageBuilder 注释适当的内容和状态
- 给定 = 提供者状态
- expectsToReceive = 描述
- 设置当提供消息时要运行的回调
- 回调必须接受一个 JSON 字符串作为参数
- 运行 Verify。如果没有出错,#winning。
$builder = new MessageBuilder(self::$config); $contents = new \stdClass(); $contents->song = 'And the wind whispers Mary'; $metadata = ['queue'=>'And the clowns have all gone to bed', 'routing_key'=>'And the clowns have all gone to bed']; $builder ->given('You can hear happiness staggering on down the street') ->expectsToReceive('footprints dressed in red') ->withMetadata($metadata) ->withContent($contents); // established mechanism to this via callbacks $consumerMessage = new ExampleMessageConsumer(); $callback = [$consumerMessage, 'ProcessSong']; $builder->setCallback($callback); $builder->verify();
提供者端消息验证
随着我们对这个实现的推进,这可能会演变。提供者严重依赖于回调。一些复杂性在于消费者和提供者在单个 pact 中有很多消息和状态。
对于每条消息,需要提供一个单独的提供者状态。这个提供者状态的名字必须是运行特定消息回调在提供者侧的键。请参阅 example\tests\MessageProvider。
- 创建你的回调和状态,封装在可调用对象中
- 数组键是消费者侧的提供者状态 / 给定(given)
- 如果您需要自定义要传递的参数,将整个内容封装在 lambda 中是有帮助的
- 选择你的验证方法
- 如果没有出错,#winning
$callbacks = array(); // a hello message is a provider state / given() on the consumer side $callbacks["a hello message"] = function() { $content = new \stdClass(); $content->text ="Hello Mary"; $metadata = array(); $metadata['queue'] = "myKey"; $provider = (new ExampleMessageProvider()) ->setContents($content) ->setMetadata($metadata); return $provider->Build(); }; $verifier = (new MessageVerifier($config)) ->setCallbacks($callbacks) ->verifyFiles([__DIR__ . '/../../output/test_consumer-test_provider.json']);
pact-stub-service 的用法
如果您想使用 fixtures 进行测试,可以使用 pact-stub-service 如此
$pactLocation = __DIR__ . '/someconsumer-someprovider.json'; $host = 'localhost'; $port = 7201; $endpoint = 'test'; $config = (new StubServerConfig()) ->setPactLocation($pactLocation) ->setHost($host) ->setPort($port) ->setEndpoint($endpoint); $stubServer = new StubServer($config); $stubServer->start(); $service = new StubServerHttpService(new GuzzleClient(), $config); echo $service->getJson(); // output: {"results":[{"name":"Games"}]}