• Stars
    star
    244
  • Rank 165,491 (Top 4 %)
  • Language
    PHP
  • License
    Apache License 2.0
  • Created over 7 years ago
  • Updated 11 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

PHP version of Pact. Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project

Pact PHP

pact-php Packagist

Downloads Downloads This Month

PHP version of Pact. Enables consumer driven contract testing. Please read the Pact.io for specific information about PACT.

Table of contents

Versions

9.X updates internal dependencies and libraries including pact-ruby-standalone v2.x which adds support for ARM64 CPU's for Linux/MacOS and providing x86 and x86_64 Windows via pact-ruby-standalone v2.x. This results in dropping PHP 7.4

8.X updates internal dependencies and libraries. This results in dropping PHP 7.3

7.x updates internal dependencies and libraries, mostly to Guzzle 7.X. This results in dropping support for PHP 7.2. 6.x updates internal dependencies, mostly surrounding the Amp library. This results in dropping support for PHP 7.1.

5.X adds preliminary support for async messages and pact specification 3.X. This does not yet support the full pact specification 3.X as the backend implementations are incomplete. However, pact-messages are supported.

The 4.X tags are accompany changes in PHPUnit 7.X which requires a PHP 7.1 or higher. Thus, 4.X drops support for PHP 7.0.

The 3.X tags are a major breaking change to the 2.X versions. To be similar to the rest of the Pact ecosystem, Pact-PHP migrated to leverage the Ruby backend. This mirrors the .Net, JS, Python, and Go implementations.

If you wish to stick with the 2.X implementation, you can continue to pull from the latest 2.X.X tag.

Specifications

The 3.X version is the version of Pact-PHP, not the pact specification version that it supports. Pact-Php 3.X-9.x supports up to Pact-Specification 2.X.

Looking for Pact-Specification 3.X and upwards. See #326

ย Supported Platforms

OS Architecture Supported Pact-PHP Version
OSX x86_64 โœ… All
Linux x86_64 โœ… All
OSX arm64 โœ… 9.x +
Linux arm64 โœ… 9.x +
Windows x86_64 โœ… All
Windows x86 โœ… All

Installation

Install the latest version with:

$ composer require pact-foundation/pact-php --dev

Composer hosts older versions under mattersight/phppact, which is abandoned. Please convert to the new package name.

Basic Consumer Usage

All of the following code will be used exclusively for the Consumer.

Start and Stop the Mock Server

This library contains a wrapper for the Ruby Standalone Mock Service.

The easiest way to configure this is to use a PHPUnit Listener. A default listener is included in this project, see 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 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:

<?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();

Create Consumer Unit Test

Create a standard PHPUnit test case class and function.

Click here to see the full sample file.

Create Mock Request

This will define what the expected request coming from your http service will look like.

$request = new ConsumerRequest();
$request
    ->setMethod('GET')
    ->setPath('/hello/Bob')
    ->addHeader('Content-Type', 'application/json');

You can also create a body just like you will see in the provider example.

Create Mock Response

This will define what the response from the provider should look like.

$matcher = new Matcher();

$response = new ProviderResponse();
$response
    ->setStatus(200)
    ->addHeader('Content-Type', 'application/json')
    ->setBody([
        'message' => $matcher->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.

$matcher = new Matcher();

$response = new ProviderResponse();
$response
    ->setStatus(200)
    ->addHeader('Content-Type', 'application/json')
    ->setBody([
        'list' => $matcher->eachLike([
            'firstName' => 'Bob',
            'age' => 22
        ])
    ]);
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')
email Regex to match an address. Value ([email protected]) $matcher->email('[email protected]')

Build the Interaction

Now that we have the request and response, we need to build the interaction and ship it over to the mock server.

// 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.

Make the Request

$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.

Verify Interactions

Verify that all interactions took place that were registered. This typically should be in each test, that way the test that failed to verify is marked correctly.

$builder->verify();

Make Assertions

Verify that the data you would expect given the response configured is correct.

$this->assertEquals('Hello, Bob', $result); // Make your assertions.

Basic Provider Usage

All of the following code will be used exclusively for Providers. This will run the Pacts against the real Provider and either verify or fail validation on the Pact Broker.

Create Unit Test

Create a single unit test function. This will test a single consumer of the service.

Start API

Get an instance of the API up and running. Click here for some tips.

If you need to set up the state of your API before making each request please see Set Up Provider State.

Provider Verification

There are three ways to verify Pact files. See the examples below.

Verify From Pact Broker

This will grab the Pact file from a Pact Broker and run the data against the stood up 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('http://localhost:58000')) // URL of the Provider.
    ->setBrokerUri(new Uri('http://localhost')) // 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.');
Verify All from Pact Broker

This will grab every Pact file associated with the given provider.

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('http://localhost:58000')) // URL of the Provider.
        ->setBrokerUri(new Uri('http://localhost')) // 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.');
}
Verify Files by Path

This allows local Pact file testing.

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('http://localhost:58000')) // URL of the Provider.
        ->setBrokerUri(new Uri('http://localhost')) // 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.');
}

Tips

Starting API Asynchronously

You can use the built in PHP server to accomplish this during your tests setUp function. The Symfony Process library can be used to run the process asynchronous.

PHP Server

Symfony Process

Set Up Provider State

The PACT verifier is a wrapper of the Ruby Standalone Verifier. See API with Provider States for more information on how this works. Since most PHP rest APIs are stateless, this required some thought.

Here are some options:

  1. Write the posted state to a file and use a factory to decide which mock repository class to use based on the state.
  2. Set up your database to meet the expectations of the request. At the start of each request, you should first reset the database to its original state.

No matter which direction you go, you will have to modify something outside of the PHP process because each request to your server will be stateless and independent.

Additional Examples

There is a separate repository with an end to end example for both the 2.X and 3.X implementations.

Message support

This feature is preliminary as the Pact community as a whole is flushing this out. The goal is not to test the transmission of an object over a bus but instead vet the contents of the message. While examples included focus on a Rabbit MQ, the exact message queue is irrelevant. Initial comparisons require a certain object type to be created by the Publisher/Producer and the Consumer of the message. This includes a metadata set where you can store the key, queue, exchange, etc that the Publisher and Consumer agree on. The content format needs to be JSON.

To take advantage of the existing pact-verification tools, the provider side of the equation stands up an http proxy to callback to processing class. Aside from changing default ports, this should be transparent to the users of the libary.

Both the provider and consumer side make heavy use of lambda functions.

Consumer Side Message Processing

The examples provided are pretty basic. See examples\tests\MessageConsumer.

  1. Create the content and metadata (array)
  2. Annotate the MessageBuilder appropriate content and states
    1. Given = Provider State
    2. expectsToReceive = Description
  3. Set the callback you want to run when a message is provided
    1. The callback must accept a JSON string as a parameter
  4. Run Verify. If nothing blows up, #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();

Provider Side Message Validation

This may evolve as we work through this implementation. The provider relies heavily on callbacks. Some of the complexity lies in a consumer and provider having many messages and states between the each other in a single pact.

For each message, one needs to provide a single provider state. The name of this provider state must be the key to run a particular message callback on the provider side. See example\tests\MessageProvider

  1. Create your callbacks and states wrapped in a callable object
    1. The array key is a provider state / given() on the consumer side
    2. It is helpful to wrap the whole thing in a lambda if you need to customize paramaters to be passed in
  2. Choose your verification method
  3. If nothing explodes, #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']);

Usage for the optional pact-stub-service

If you would like to test with fixtures, you can use the pact-stub-service like this:

$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"}]}

More Repositories

1

pact-ruby

Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.
Ruby
2,160
star
2

pact-js

JS version of Pact. Pact is a contract testing framework for HTTP APIs and non-HTTP asynchronous messaging systems.
TypeScript
1,456
star
3

pact-jvm

JVM version of Pact. Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.
Kotlin
1,077
star
4

pact-go

Golang version of Pact. Pact is a contract testing framework for HTTP APIs and non-HTTP asynchronous messaging systems.
Go
856
star
5

pact-net

.NET version of Pact. Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.
C#
826
star
6

pact_broker

Enables your consumer driven contracts workflow
Ruby
702
star
7

pact-python

Python version of Pact. Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.
Python
501
star
8

pact-specification

Describes the pact format and verification specifications
295
star
9

pact.io

Pact Foundation Website
HTML
219
star
10

pact-js-core

Core binaries for pact-js, a Contract Testing Framework. NOTE: If you are looking to do Pact contract testing in node, you almost certainly want pact-js, not pact-node.
TypeScript
146
star
11

pact-workshop-js

Pact JS workshop - learn Pact in 60 minutes
JavaScript
129
star
12

pact-workshop-go

Golang Pact workshop
Go
106
star
13

pact-broker-docker

Dockerized Pact Broker
Shell
92
star
14

pact-reference

Reference implementations for the pact specifications
Rust
87
star
15

pact-workshop-jvm-spring

Example Spring Boot project for the Pact workshop
Java
85
star
16

jest-pact

A Pact adaptor for to allow you to easily run tests with Jest
TypeScript
80
star
17

pact-workshop-dotnet-core-v1

A workshop for Pact using .NET Core
C#
76
star
18

pact-stub-server

Standalone pact stub server
Rust
75
star
19

pact-mock_service

Provides a mock service for use with Pact
Ruby
73
star
20

pact_broker-client

A Ruby and CLI client for the Pact Broker. Publish and retrieve pacts and verification results.
Ruby
69
star
21

nestjs-pact

Injectable Pact.js Consumer/Producer for NestJS
TypeScript
48
star
22

devrel

Developer Relations @ Pact - Your map to the Pact landscape for all-comers (maintainers, contributors, users, newbies)
38
star
23

pact-ruby-standalone

A standalone pact command line executable using the ruby pact implementation and Travelling Ruby
Shell
35
star
24

pact-provider-verifier

Cross-platform, generic language, Pact provider verification tool
Ruby
28
star
25

pact-workshop-Maven-Springboot-JUnit5

Pact Maven + Springboot + JUnit5 workshop - learn Pact in 60 minutes
28
star
26

pact-stub-server-archived

Wraps the Pact Rust mock server in a Docker container
Dockerfile
25
star
27

pact-js-mocha

EXPERIMENTAL Mocha Interface for Pact JS (warning: it is not recommended for beginners)
JavaScript
18
star
28

pact-plugins

๐Ÿฐ Architecture to support Plugins ๐Ÿ”Œ with Pact ๐Ÿ”—
Rust
16
star
29

pact-provider-proxy

Allows pact verification against a running provider at a configurable base URL
Ruby
16
star
30

docs.pact.io

Pact documentation website
HTML
15
star
31

pact-broker-chart

This repository houses the Pact Broker Helm Chart
Smarty
11
star
32

karma-pact

Pact Framework Plugin for Karma
Shell
11
star
33

pact-5-minute-getting-started-guide

JavaScript
11
star
34

pact-ruby-cli

Amalgamated Pact Ruby CLI
Shell
11
star
35

pact-support

Shared code for Pact gems
Ruby
7
star
36

pact_broker-serverless

Pact Broker running in AWS Lambda with Serverless
Shell
7
star
37

pact-cplusplus

C++ DSL for Pact Library
HTML
6
star
38

pact-ruby-e2e-example

Code base to use for demonstrating features and recreating issues in the ruby implementation of pact. Please fork it and modify to recreate your own code.
Ruby
5
star
39

pact-xml

XML support for the Pact gem
Ruby
4
star
40

pact-plugin-template-golang

Pact ๐Ÿ”— Plugin ๐Ÿ”Œ template for the GoLang ๐Ÿฟ๏ธ language = ๐Ÿซถ
Go
4
star
41

pact-message-demo

Ruby
4
star
42

pact-consumer-minitest

Minitest support for the Pact Consumer gem
Ruby
4
star
43

serverless-offline-pact

A serverless offline plugin to start one or more pact stub service alongside your serverless application
TypeScript
3
star
44

pact-mock-service-docker

Docker image running the Pact mock service
Ruby
3
star
45

homebrew-pact-ruby-standalone

The Pact Ruby Standalone public homebrew tap for macos/linux homebrew formulae
Shell
3
star
46

.github

The GitHub landing page for Pact - The de-facto contract testing tool
3
star
47

pact-event-bot

TypeScript
2
star
48

pact-standalone-npm

Pact Standalone wrapper for NPM projects
JavaScript
2
star
49

pact-parser

Small server to aid using existing pacts files as data sources in unit and integrational testing.
JavaScript
2
star
50

pact-mock-service-npm

Shell
2
star
51

pact-js-cli

The Broker CLI for Pact, but available to your node scripts
TypeScript
2
star
52

grunt-pact

A grunt task to run pact-node
JavaScript
1
star
53

pactr

R version of Pact. Enables consumer driven contract testing. Please read the Pact.io for specific information about PACT.
R
1
star
54

release-gem

Github action that bumps the version, generates the changelog, releases the gem, and creates a Github release
Shell
1
star
55

mocha-pact

An adaptor to allow you to easily run pact tests with Mocha
TypeScript
1
star
56

pact-ruby-standalone-e2e-example

Code base to use for demonstrating features and recreating issues in the Ruby standalone implementation of pact. Please fork it and modify to recreate your own code.
Shell
1
star
57

cypress-pact

Pact plugin for integrating Pact with Cypress tests
1
star
58

pact-js-dev-config

Shared configs for developers working on pact-js and related projects
JavaScript
1
star
59

blog.pact.io

Ghost application setup for blog.pact.io
SCSS
1
star