Skip to content

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

License

Notifications You must be signed in to change notification settings

bethesque/pact-php

 
 

Repository files navigation

Pact PHP

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.

This is a project to provide Pact functionality completely in PHP. This started as a straight port of Pact.Net on the 1.1 specification. Overtime, the project adjusted to a more PHP way of doing things.

The namespace is PhpPact as Pact-PHP uses the namespace of PactPhp.

Composer

Run composer require mattersight/phppact

Service Consumer

1. Build your client

This is either a net new green field client you are writing in PHP or a legacy application. The key part is your client will need to inject a "mock server". To provide Windows support, this project leverages julienfalque/http-mock .

<?php
/**
 * Class MockApiConsumer
 *
 * Example consumer API client.  Note that if you will need to pass in the host  Note
 */
class MockApiConsumer
{
    /**
     * @var \PhpPact\Mocks\MockHttpService\MockProviderHost
     */
    private $_mockHost;

    /**
     * @param $host
     */
    public function setMockHost(&$host)
    {
        $this->_mockHost = $host;
    }


    /**
     * Mock out a basic GET.  Assume it returns some business value to be used in other parts of this mock api consumer/client
     *
     * @param $uri string
     * @return mixed
     */
    public function GetBasic($url)
    {
        $uri = (new \Windwalker\Uri\PsrUri($url))
            ->withPath("/");

        $httpRequest = (new \Windwalker\Http\Request\Request())
            ->withUri($uri)
            ->withAddedHeader("Content-Type", "application/json")
            ->withMethod("get");


        $response = $this->sendRequest($httpRequest);
        return $response;
    }
	
	/*
		Other examples in examples/test/MockApiConsumer.php
	*/
	
	/**
     * Encapsulate your calls to the actual api. This allows mock out of server calls
     *
     * @param \Psr\Http\Message\RequestInterface $httpRequest
     * @return callable|null|\Psr\Http\Message\ResponseInterface
     * @throws Exception
     */
    private function sendRequest(\Psr\Http\Message\RequestInterface $httpRequest)
    {
        // handle mock server
        if (isset($this->_mockHost)) {
            return $this->_mockHost->handle($httpRequest);
        }

        // make actual call to the client
        throw new \Exception("Since this is a mock api client, there is no 'real' server.  This is where you put your app logic.");
    }
}

2. Write your tests

Create a new test case within your service consumer test project, using whatever test framework you like (in this case we used phpUnit). Then implement your tests.

<?php

require_once( __DIR__ . '/MockApiConsumer.php');

use PHPUnit\Framework\TestCase;

class ConsumerTest extends TestCase
{

    /**
     * @var \PhpPact\PactBuilder
     */
    protected $_build;

    const CONSUMER_NAME = "MockApiConsumer";
    const PROVIDER_NAME = "MockApiProvider";


    /**
     * Before each test, rebuild the builder
     */
    protected function setUp()
    {
        parent::setUp();
        $this->_build = new \PhpPact\PactBuilder();
        $this->_build->ServiceConsumer(self::CONSUMER_NAME)
            ->HasPactWith(self::PROVIDER_NAME);
    }

    protected function tearDown()
    {
        parent::tearDown();

        unset($this->_build);
    }

    public function testGetBasic()
    {
        // build the request
        $reqHeaders = array();
        $reqHeaders["Content-Type"] = "application/json";
        $request = new \PhpPact\Mocks\MockHttpService\Models\ProviderServiceRequest(\PhpPact\Mocks\MockHttpService\Models\HttpVerb::GET, "/", $reqHeaders);

        // build the response
        $resHeaders = array();
        $resHeaders["Content-Type"] = "application/json";
        $resHeaders["AnotherHeader"] = "my-header";

        $response = new \PhpPact\Mocks\MockHttpService\Models\ProviderServiceResponse('200', $resHeaders);
        $response->setBody("{\"msg\" : \"I am the walrus\"}");

        // build up the expected results and appropriate responses
        $mockService = $this->_build->getMockService();
        $mockService->Given("Basic Get Request")
            ->UponReceiving("A GET request with a base / path and a content type of json")
            ->With($request)
            ->WillRespondWith($response);

        // build system under test
        $host = $mockService->getHost();

        $clientUnderTest = new MockApiConsumer();
        $clientUnderTest->setMockHost($host);
        $receivedResponse = $clientUnderTest->GetBasic("http://localhost");

        // do some asserts on the return
        $this->assertEquals('200', $receivedResponse->getStatusCode(), "Let's make sure we have an OK response");

        // verify the interactions
        $hasException = false;
        try {
            $results = $mockService->VerifyInteractions();

        } catch (\PhpPact\PactFailureException $e) {
            $hasException = true;
        }
        $this->assertFalse($hasException, "This basic get should verify the interactions and not throw an exception");
    }
}

3. Run the test

Everything should be green

Service Provider

1. Build API

Get an instance of the API up and running. If your API support PHP's built-in web server, see this great tutorial on bootstrapping phpunit to spin up the API, run tests, and tear down the API. See examples/site/provider.php and examples/test/bootstrap.php for a local server on Windows.

2. Configure PHP unit

Bootstrap PHPUnit with appropriate composer and autoloaders. Optionally, add a bootstrap api from the Build API section.

3. Tell the provider it needs to honour the pact

<?php

// Pick your PSR client.  Guzzle should work as well.
$httpClient = new \Windwalker\Http\HttpClient();

$pactVerifier->ProviderState("A GET request to get types")
                ->ServiceProvider("MockApiProvider", $httpClient)
                ->HonoursPactWith("MockApiConsumer")
                ->PactUri('../pact/mockapiconsumer-mockapiprovider.json')
                ->Verify();

4. Run the test

Everything should be green

Other Notes

This was tested and used on Windows 10. Below are the versions of PHP:

  • PHPUnit 6.2.2
  • PHP 7.1.4

Key Examples

Provider setUp and tearDown

The setUp and tearDown on a per Provider test basis needs to use closures

<?php

require_once( __DIR__ . '/MockApiConsumer.php');

use PHPUnit\Framework\TestCase;

class ProviderTest extends TestCase
{
    public function testPactProviderStateSetupTearDown() 
    {
        $httpClient = new \Windwalker\Http\HttpClient();

        $pactVerifier = new \PhpPact\PactVerifier($uri);

        $setUpFunction = function() {
            $fileName = "mock.json";
            $currentDir = dirname(__FILE__);
            $absolutePath = realpath($currentDir . '/../site/' );
            $absolutePath .= '/' . $fileName;

            $type = new \stdClass();
            $type->id = 700;
            $type->name = "mock";
            $types = array( $type );
            $body = new \stdClass();

            $body->types = $types;

            $output = \json_encode($body, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);

            file_put_contents($absolutePath, $output);
        };

        $tearDownFunction = function() {
            $fileName = "mock.json";
            $currentDir = dirname(__FILE__);
            $absolutePath = realpath($currentDir . '/../site/' );
            $absolutePath .= '/' . $fileName;

            unlink($absolutePath);
        };

        $pactVerifier->ProviderState("A GET request for a setup", $setUpFunction, $tearDownFunction);
    }         
}

Provider Test Filtering

If you want to filter down the interaction you want to test, pass in options to Verify(). Try the below.

<?php

// declare your state
$testState = "There is something to POST to";

// Pick your PSR client.  Guzzle should work as well.
$httpClient = new \Windwalker\Http\HttpClient();

$pactVerifier->ProviderState("Test State")
    ->ServiceProvider("MockApiProvider", $httpClient)
    ->HonoursPactWith("MockApiConsumer")
    ->PactUri($json)
    ->Verify(null, $testState);
  

Pact Broker Integration

To integrate with your pact broker host, there are several options. This section focuses on the PactBrokerConnector. To be fair, the pact broker authentication is currently untested but mirrors the implementation in pact-js.

Publishing Pacts

There are several hopefully self explanatory functions in PactBrokerConnector:

  • PublishFile - reads the JSON from a file
  • PublishJson - publishes from a JSON string
  • PublishPact - publishes from a ProviderServicePactFile object
<?php

// create your options
$uriOptions = new \PhpPact\PactUriOptions("http://your-pact-broker" );
$connector = new \PhpPact\PactBrokerConnector($uriOptions);

// Use the appropriate function to read from a file, JSON string, or ProviderServicePactFile object
$file = __DIR__ . '/../example/pact/mockapiconsumer-mockapiprovider.json';
$statusCode = $connector->PublishFile($file, '1.0.3');

Retrieving Pacts

If you have an open pact broker, $pactVerifier->PactUri uses file_get_contents which accepts a URL. You could simply use this technique in those cases.

To do some more robust interactions, There are several hopefully self explanatory functions in PactBrokerConnector:

  • RetrieveLatestProviderPacts - retrieve all the latest pacts associated with this provider
  • RetrievePact - retrieve particular pact
<?php

// create your options
$uriOptions = new \PhpPact\PactUriOptions("http://your-pact-broker" );
$connector = new \PhpPact\PactBrokerConnector($uriOptions);

// particular version
$pact = $connector->RetrievePact("MockApiProvider", "MockApiConsumer", "1.0.2");
error_log(\json_encode($pact,JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));

// get all pacts for this provider
$pacts = $connector->RetrieveLatestProviderPacts("MockApiProvider");
$pact = array_pop($pacts);
error_log(\json_encode($pact,JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));

Project Tests

To run Pact-Php-Native tests, there are several phpunit.xml files. The provider tests use a Windows method to shutdown the mock server. Root is expected to be the root of Pact Php Native

  • All tests: php .\vendor\phpunit\phpunit\phpunit -c .\phpunit-all-tests.xml
  • Provider Example: php .\vendor\phpunit\phpunit\phpunit -c .\phpunit-provider-test.xml
  • Consumer Example: php .\vendor\phpunit\phpunit\phpunit -c .\phpunit-consumer-test.xml

Related Projects

About

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

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • PHP 100.0%