Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 21 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ All of the following code will be used exclusively for the Consumer.

This library contains a wrapper for the [Ruby Standalone Mock Service](https://github.com/pact-foundation/pact-mock_service).

The easiest way to configure this is to use a [PHPUnit Listener](https://phpunit.de/manual/current/en/appendixes.configuration.html#appendixes.configuration.test-listeners). A default listener is included in this project, see [PactTestListener.php](/src/PhpPact/Consumer/Listener/PactTestListener.php). This utilizes environmental variables for configurations. These env variables can either be added to the system or to the phpunit.xml configuration file. Here is an example [phpunit.xml](/example/phpunit.consumer.xml) file configured to use the default. Keep in mind that both the test suite and the arguments array must be the same value.
The easiest way to configure this is to use a [PHPUnit Extension](https://phpunit.readthedocs.io/en/7.1/configuration.html#registering-testrunner-extensionsl). A default extension is included in this project, see [PactExtension.php](src/PhpPact/Consumer/Hook/PactExtension.php). This utilizes environmental variables for configurations. These env variables can either be added to the system or to the phpunit.xml configuration file. Here is an example [phpunit.xml](example/phpunit.consumer.xml) file configured to use the default. Keep in mind that both the test suite and the arguments array must be the same value.

Alternatively, you can start and stop as in whatever means you would like by following this example:

Expand Down Expand Up @@ -103,7 +103,7 @@ Alternatively, you can start and stop as in whatever means you would like by fol

Create a standard PHPUnit test case class and function.

[Click here](/example/tests/Consumer/Service/ConsumerServiceHelloTest.php) to see the full sample file.
[Click here](example/tests/Consumer/Service/ConsumerServiceHelloTest.php) to see the full sample file.

### Create Mock Request

Expand All @@ -124,28 +124,24 @@ You can also create a body just like you will see in the provider example.
This will define what the response from the provider should look like.

```php
$matcher = new Matcher();

$response = new ProviderResponse();
$response
->setStatus(200)
->addHeader('Content-Type', 'application/json')
->setBody([
'message' => $matcher->regex('Hello, Bob', '(Hello, )[A-Za-z]')
'message' => Match::regex('Hello, Bob', '(Hello, )[A-Za-z]')
]);
```

In this example, we are using matchers. This allows us to add flexible rules when matching the expectation with the actual value. In the example, you will see regex is used to validate that the response is valid.

```php
$matcher = new Matcher();

$response = new ProviderResponse();
$response
->setStatus(200)
->addHeader('Content-Type', 'application/json')
->setBody([
'list' => $matcher->eachLike([
'list' => Match::eachLike([
'firstName' => 'Bob',
'age' => 22
])
Expand All @@ -154,23 +150,23 @@ $response

Matcher | Explanation | Parameters | Example
---|---|---|---
term | Match a value against a regex pattern. | Value, Regex Pattern | $matcher->term('Hello, Bob', '(Hello, )[A-Za-z]')
regex | Alias to term matcher. | Value, Regex Pattern | $matcher->regex('Hello, Bob', '(Hello, )[A-Za-z]')
dateISO8601 | Regex match a date using the ISO8601 format. | Value (Defaults to 2010-01-01) | $matcher->dateISO8601('2010-01-01')
timeISO8601 | Regex match a time using the ISO8601 format. | Value (Defaults to T22:44:30.652Z) | $matcher->timeISO8601('T22:44:30.652Z')
dateTimeISO8601 | Regex match a datetime using the ISO8601 format. | Value (Defaults to 2015-08-06T16:53:10+01:00) | $matcher->dateTimeISO8601('2015-08-06T16:53:10+01:00')
dateTimeWithMillisISO8601 | Regex match a datetime with millis using the ISO8601 format. | Value (Defaults to 2015-08-06T16:53:10.123+01:00) | $matcher->dateTimeWithMillisISO8601('2015-08-06T16:53:10.123+01:00')
timestampRFC3339 | Regex match a timestamp using the RFC3339 format. | Value (Defaults to Mon, 31 Oct 2016 15:21:41 -0400) | $matcher->timestampRFC3339('Mon, 31 Oct 2016 15:21:41 -0400')
like | Match a value against its data type. | Value | $matcher->like(12)
somethingLike | Alias to like matcher. | Value | $matcher->somethingLike(12)
eachLike | Match on an object like the example. | Value, Min (Defaults to 1) | $matcher->eachLike(12)
boolean | Match against boolean true. | none | $matcher->boolean()
integer | Match a value against integer. | Value (Defaults to 13) | $matcher->integer()
decimal | Match a value against float. | Value (Defaults to 13.01) | $matcher->decimal()
hexadecimal | Regex to match a hexadecimal number. Example: 3F | Value (Defaults to 3F) | $matcher->hexadecimal('FF')
uuid | Regex to match a uuid. | Value (Defaults to ce118b6e-d8e1-11e7-9296-cec278b6b50a) | $matcher->uuid('ce118b6e-d8e1-11e7-9296-cec278b6b50a')
ipv4Address | Regex to match a ipv4 address. | Value (Defaults to 127.0.0.13) | $matcher->ipv4Address('127.0.0.1')
ipv6Address | Regex to match a ipv6 address. | Value (Defaults to ::ffff:192.0.2.128) | $matcher->ipv6Address('::ffff:192.0.2.1')
term | Match a value against a regex pattern. | Value, Regex Pattern | Match::term('Hello, Bob', '(Hello, )[A-Za-z]')
regex | Alias to term matcher. | Value, Regex Pattern | Match::regex('Hello, Bob', '(Hello, )[A-Za-z]')
dateISO8601 | Regex match a date using the ISO8601 format. | Value (Defaults to 2010-01-01) | Match::dateISO8601('2010-01-01')
timeISO8601 | Regex match a time using the ISO8601 format. | Value (Defaults to T22:44:30.652Z) | Match::timeISO8601('T22:44:30.652Z')
dateTimeISO8601 | Regex match a datetime using the ISO8601 format. | Value (Defaults to 2015-08-06T16:53:10+01:00) | Match::dateTimeISO8601('2015-08-06T16:53:10+01:00')
dateTimeWithMillisISO8601 | Regex match a datetime with millis using the ISO8601 format. | Value (Defaults to 2015-08-06T16:53:10.123+01:00) | Match::dateTimeWithMillisISO8601('2015-08-06T16:53:10.123+01:00')
timestampRFC3339 | Regex match a timestamp using the RFC3339 format. | Value (Defaults to Mon, 31 Oct 2016 15:21:41 -0400) | Match::timestampRFC3339('Mon, 31 Oct 2016 15:21:41 -0400')
like | Match a value against its data type. | Value | Match::like(12)
somethingLike | Alias to like matcher. | Value | Match::somethingLike(12)
eachLike | Match on an object like the example. | Value, Min (Defaults to 1) | Match::eachLike(12)
boolean | Match against boolean true. | none | Match::boolean()
integer | Match a value against integer. | Value (Defaults to 13) | Match::integer()
decimal | Match a value against float. | Value (Defaults to 13.01) | Match::decimal()
hexadecimal | Regex to match a hexadecimal number. Example: 3F | Value (Defaults to 3F) | Match::hexadecimal('FF')
uuid | Regex to match a uuid. | Value (Defaults to ce118b6e-d8e1-11e7-9296-cec278b6b50a) | Match::uuid('ce118b6e-d8e1-11e7-9296-cec278b6b50a')
ipv4Address | Regex to match a ipv4 address. | Value (Defaults to 127.0.0.13) | Match::ipv4Address('127.0.0.1')
ipv6Address | Regex to match a ipv6 address. | Value (Defaults to ::ffff:192.0.2.128) | Match::ipv6Address('::ffff:192.0.2.1')

### Build the Interaction

Expand Down
14 changes: 3 additions & 11 deletions example/phpunit.consumer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,9 @@
<directory>./tests/Consumer</directory>
</testsuite>
</testsuites>
<listeners>
<listener class="PhpPact\Consumer\Listener\PactTestListener">
<arguments>
<array>
<element key="0">
<string>PhpPact Example Tests</string>
</element>
</array>
</arguments>
</listener>
</listeners>
<extensions>
<extension class="PhpPact\Consumer\Hook\PactExtension" />
</extensions>
<php>
<env name="PACT_MOCK_SERVER_HOST" value="localhost"/>
<env name="PACT_MOCK_SERVER_PORT" value="7200"/>
Expand Down
120 changes: 120 additions & 0 deletions src/PhpPact/Consumer/Hook/PactExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php

namespace PhpPact\Consumer\Hook;

use function getenv;
use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\Psr7\Uri;
use PhpPact\Broker\Service\BrokerHttpClient;
use PhpPact\Http\GuzzleClient;
use PhpPact\Standalone\MockService\MockServer;
use PhpPact\Standalone\MockService\MockServerConfigInterface;
use PhpPact\Standalone\MockService\MockServerEnvConfig;
use PhpPact\Standalone\MockService\Service\MockServerHttpService;
use PHPUnit\Runner\AfterLastTestHook;
use PHPUnit\Runner\AfterTestFailureHook;
use PHPUnit\Runner\BeforeFirstTestHook;
use Throwable;

class PactExtension implements BeforeFirstTestHook, AfterLastTestHook, AfterTestFailureHook
{
/**
* @var MockServer|null
*/
private $server = null;

/**
* @var MockServerConfigInterface
*/
private $mockServerConfig;

/**
* @var bool
*/
private $failed = false;

public function __construct()
{
$this->mockServerConfig = new MockServerEnvConfig();
}

public function executeBeforeFirstTest(): void
{
$this->server = new MockServer($this->mockServerConfig);
$this->server->start();
}

public function executeAfterTestFailure(string $test, string $message, float $time): void
{
$this->failed = true;
}

public function executeAfterLastTest(): void
{
try {
$httpService = new MockServerHttpService(new GuzzleClient(), $this->mockServerConfig);
$httpService->verifyInteractions();

$pact = $httpService->getPactJson();
} catch (ServerException $exception) {
print $exception->getResponse()->getBody()->getContents();
exit(1);
} catch (Throwable $throwable) {
print $throwable->getMessage();
exit(1);
} finally {
$this->server->stop();
}

$this->uploadPact($pact);
}

protected function uploadPact(string $pact): void
{
if ($this->failed === true) {
print 'A unit test has failed. Skipping PACT file upload.';
return;
}

if (!($pactBrokerUri = getenv('PACT_BROKER_URI'))) {
print 'PACT_BROKER_URI environment variable was not set. Skipping PACT file upload.';
return;
}

if (!($consumerVersion = getenv('PACT_CONSUMER_VERSION'))) {
print 'PACT_CONSUMER_VERSION environment variable was not set. Skipping PACT file upload.';
return;
}

if (!($tag = getenv('PACT_CONSUMER_TAG'))) {
print 'PACT_CONSUMER_TAG environment variable was not set. Skipping PACT file upload.';
return;
}

$clientConfig = [];
if (($user = getenv('PACT_BROKER_HTTP_AUTH_USER')) &&
($pass = getenv('PACT_BROKER_HTTP_AUTH_PASS'))
) {
$clientConfig = [
'auth' => [$user, $pass],
];
}

if (($sslVerify = getenv('PACT_BROKER_SSL_VERIFY'))) {
$clientConfig['verify'] = $sslVerify !== 'no';
}

$headers = [];
if ($bearerToken = getenv('PACT_BROKER_BEARER_TOKEN')) {
$headers['Authorization'] = 'Bearer ' . $bearerToken;
}

$client = new GuzzleClient($clientConfig);

$brokerHttpService = new BrokerHttpClient($client, new Uri($pactBrokerUri), $headers);
$brokerHttpService->tag($this->mockServerConfig->getConsumer(), $consumerVersion, $tag);
$brokerHttpService->publishJson($pact, $consumerVersion);

print 'Pact file has been uploaded to the Broker successfully.';
}
}
13 changes: 13 additions & 0 deletions src/PhpPact/Consumer/InteractionBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public function __construct(MockServerConfigInterface $config)
}

/**
* @deprecated use register instead
* @param string $providerState what is given to the request
*
* @return InteractionBuilder
Expand All @@ -48,6 +49,7 @@ public function given(string $providerState): self
}

/**
* @deprecated use register instead
* @param string $description what is received when the request is made
*
* @return InteractionBuilder
Expand All @@ -60,6 +62,7 @@ public function uponReceiving(string $description): self
}

/**
* @deprecated use register instead
* @param ConsumerRequest $request mock of request sent
*
* @return InteractionBuilder
Expand All @@ -72,6 +75,7 @@ public function with(ConsumerRequest $request): self
}

/**
* @deprecated use register instead
* Make the http request to the Mock Service to register the interaction.
*
* @param ProviderResponse $response mock of response received
Expand All @@ -85,6 +89,15 @@ public function willRespondWith(ProviderResponse $response): bool
return $this->mockServerHttpService->registerInteraction($this->interaction);
}

/**
* Make the http request to the Mock Service to register the interaction.
* @return bool returns true on success
*/
public function register(Interaction $interaction): bool
{
return $this->mockServerHttpService->registerInteraction($interaction);
}

/**
* {@inheritdoc}
*/
Expand Down
9 changes: 9 additions & 0 deletions src/PhpPact/Consumer/Listener/PactTestListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PhpPact\Consumer\Listener;

use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\Psr7\Uri;
use PhpPact\Broker\Service\BrokerHttpClient;
use PhpPact\Http\GuzzleClient;
Expand All @@ -15,8 +16,10 @@
use PHPUnit\Framework\TestListener;
use PHPUnit\Framework\TestListenerDefaultImplementation;
use PHPUnit\Framework\TestSuite;
use Throwable;

/**
* @deprecated use PhpPact\Consumer\Hook\PactExtension instead, PHPunit deprecated the use of listeners
* PACT listener that can be used with environment variables and easily attached to PHPUnit configuration.
* Class PactTestListener
*/
Expand Down Expand Up @@ -89,6 +92,12 @@ public function endTestSuite(TestSuite $suite): void
$httpService->verifyInteractions();

$json = $httpService->getPactJson();
} catch (ServerException $exception) {
print $exception->getResponse()->getBody()->getContents();
exit(1);
} catch (Throwable $throwable) {
print $throwable->getMessage();
exit(1);
} finally {
$this->server->stop();
}
Expand Down
Loading