pact-foundation/pact-php
PHP implementation of the Pact consumer-driven contract testing framework. Define contracts between services, run provider verification, and integrate with PHPUnit/CI to prevent breaking API changes. Supports HTTP interactions, mock servers, and Pact Broker workflows.
Install the Package
composer require pact-foundation/pact-php
For consumer-driven testing, use pact-foundation/pact-php-consumer; for provider verification, use pact-foundation/pact-php-provider.
Configure Pact
Add to composer.json:
"config": {
"pact": {
"consumer": "YourConsumerApp",
"provider": "YourProviderApp",
"broker": "http://localhost:9292" // Optional: Pact Broker URL
}
}
Or set via environment variables (e.g., .env):
PACT_CONSUMER=YourConsumerApp
PACT_PROVIDER=YourProviderApp
First Consumer Test
use Pact\Consumer\Message\Message;
use Pact\Consumer\Interaction;
use Pact\Consumer\Model\Consumer;
$pact = new Consumer('YourConsumerApp', new Provider('YourProviderApp', 'localhost', 1234));
$pact->given('a default state')
->uponReceiving('a request to fetch user')
->withRequest('get', '/users/1', [], '{"id":1}')
->willRespondWith(200, ['Content-Type' => 'application/json'], '{"id":1,"name":"John"}');
$pact->verify();
First Provider Test
use Pact\Provider\Verifier\Verifier;
use Pact\Provider\Verifier\ProviderVerifier;
$verifier = new ProviderVerifier(
new Verifier(),
'YourProviderApp',
'localhost',
1234,
'http://localhost:9292' // Pact Broker
);
$verifier->verify();
Define Contracts in Tests
Use Pact\Consumer\MockServiceBuilder to mock provider interactions:
$mockServer = new MockServiceBuilder($pact)
->setPort(1234)
->setHostName('localhost')
->build();
$mockServer->start();
Run tests against the mock server to ensure consumer expectations are met.
Publish Pact Files After tests pass, publish the pact file to a Pact Broker:
$pact->publishToBroker('http://localhost:9292');
Or via CLI:
./vendor/bin/pact-publish --pact-dir=./pacts --broker-base-url=http://localhost:9292
Integrate with CI/CD
Verify Contracts
Use ProviderVerifier to validate that the provider adheres to published contracts:
$verifier = new ProviderVerifier(
new Verifier(),
'YourProviderApp',
'localhost',
8000,
'http://localhost:9292'
);
$verifier->verify();
Run this in CI to catch breaking changes early.
Handle Dynamic Data Use state handlers for dynamic responses:
$pact->given('a user exists with id 1')
->uponReceiving('a request to fetch user')
->withRequest('get', '/users/1')
->willRespondWith(200, [], '{"id":1,"name":"John"}')
->toState('user_1_exists');
In your provider, implement a route to transition to this state:
Route::get('/setup/user_1', function () {
User::create(['id' => 1, 'name' => 'John']);
return response()->json(['status' => 'ready']);
});
Partial Verification For large providers, verify contracts incrementally:
$verifier->verifyProviderState('a default state');
Laravel Service Provider
Bootstrap Pact in AppServiceProvider:
public function boot()
{
if ($this->app->environment('testing') && config('pact.enabled')) {
$mockServer = (new MockServiceBuilder(config('pact')))
->setPort(config('pact.port'))
->build();
$mockServer->start();
}
}
Testing HTTP Clients Use Pact with Laravel HTTP tests:
public function test_fetch_user()
{
$response = Http::get('http://localhost:1234/users/1');
$response->assertOk();
}
Mocking External APIs Replace real API calls with Pact mocks in unit/integration tests:
$mockServer = new MockServiceBuilder($pact)
->setScheme('https')
->setHostName('api.example.com')
->build();
Port Conflicts
1234. Ensure no other service is using it.$mockServer->setPort(1235);
State Management
Broker Authentication
$verifier->setBrokerAuth('username', 'password');
Dynamic Data in Responses
$timestamp and replace them in the provider:
->willRespondWith(200, [], '{"created_at":"$timestamp"}')
Enable Verbose Logging
$pact->setLogDir(__DIR__.'/logs');
$pact->setLogLevel(LogLevel::DEBUG);
Inspect Pact Files
./pacts by default. Validate them manually:
pact-cli validate ./pacts/consumer-provider.json
Provider Verification Failures
--verbose for detailed output:
./vendor/bin/pact-verify-provider --provider YourProviderApp --pact-dir ./pacts --verbose
Custom Matchers Extend Pact’s matchers for complex assertions:
use Pact\Consumer\Model\Matchers\RegexMatcher;
$pact->given('...')
->uponReceiving('...')
->withRequest('post', '/users', [
'body' => new RegexMatcher('/{"name":"\w+"}/')
]);
Custom Providers
Implement Pact\Provider\Provider for non-HTTP protocols (e.g., gRPC):
class CustomProvider implements Provider {
public function verify(): void {
// Custom logic
}
}
Plugin System Use Pact’s plugin architecture to integrate with tools like:
Environment-Specific Config Use Laravel’s config caching to switch between dev/staging/prod:
// config/pact.php
'enabled' => env('PACT_ENABLED', false),
'port' => env('PACT_PORT', 1234),
Docker Integration Run Pact Broker in Docker:
# docker-compose.yml
services:
pact_broker:
image: pactfoundation/pact-broker
ports:
- "9292:9292"
Parallel Testing
How can I help you explore Laravel packages today?