Download the PHP package elkady/laravel-pact without Composer
On this page you can find all versions of the php package elkady/laravel-pact. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download elkady/laravel-pact
More information about elkady/laravel-pact
Files in elkady/laravel-pact
Package laravel-pact
Short Description Laravel-native consumer-driven contract testing. A framework ergonomics layer on top of pact-foundation/pact-php.
License MIT
Informations about the package laravel-pact
laravel-pact
Consumer-driven contract testing that feels like Laravel.
Why this exists
This is a wrapper around pact-foundation/pact-php, not a replacement for it.
pact-php is excellent, actively maintained and spec-compliant. It is a general PHP library, though, and four things about using it in a Laravel app are genuinely painful. This package fixes those four things and delegates everything else — the protocol, the mock server, the matching engine, the pact file format — straight to pact-php.
pact-foundation/pact-php |
laravel-pact |
|
|---|---|---|
| Pact protocol, mock server, matching engine | ✅ owns it | ↳ delegates to pact-php |
| Pact file format | ✅ owns it | ↳ delegates to pact-php |
Provider states under RefreshDatabase |
states run over HTTP in a separate process — state leaks between interactions | each interaction in its own transaction, rolled back immediately |
| Consumer DSL | generic PHP builder objects | fluent, Http::fake()-shaped, integrated with the Http facade |
| Queue / event contracts | HTTP + raw message pacts | serialises real Laravel events and jobs via the framework's own path |
| Test generation from code | — | #[PactConsumes] / #[PactProvides] → real, committed test files |
| Broker workflow | verifier-level only | pact:publish, pact:can-i-deploy, pact:verify as Artisan commands |
| Failure output | nested JSON | formatted diffs naming the interaction and its provider state |
The problem it really solves
Pact's verifier is a Rust binary reached over FFI. It blocks the calling process and drives your provider over real HTTP — so run the ordinary way, your provider is a different process from PHPUnit. That means:
RefreshDatabasewraps the test process in a transaction the provider cannot see;- provider states run through an HTTP callback outside any transaction, so rows created for interaction #1 are still there for interaction #7 — verification quietly depends on interaction order;
- fakes, spies and frozen clocks bound in your test's container have no effect on the code being verified.
laravel-pact turns the arrangement inside out. The verifier is pushed into a child process, and the provider becomes the PHPUnit process itself, served by a small in-process listener. Interactions are dispatched through your app's own HTTP kernel — same container, same connection, same open transaction.
The verifier brackets every interaction with a setup and teardown callback,
which become the transaction boundary:
Because it nests, it composes with RefreshDatabase rather than fighting it.
tests/Feature/TransactionalIsolationTest.php
proves both halves: with the fix each interaction sees exactly its own rows
([1, 1]), and with the fix disabled the second interaction sees the first
one's leaked data ([1, 2]).
Installation
Requires PHP 8.2+, Laravel 11/12/13, and ext-ffi (pact-php drives the Pact
core through it).
Quickstart
1. Consumer test
BillingClient makes an ordinary Http::get('https://billing.internal/...')
call. InteractsWithPact registers one global request middleware on Laravel's
HTTP client factory — the same machinery Http::fake() uses — so the request is
transparently redirected to the mock server. Your application code needs no
knowledge of Pact.
Pact::verifyInteractions() asserts every declared interaction was actually
exercised, then writes .pact/checkout-service-billing-service.json. An
interaction nobody called fails the test, so dead contracts cannot accumulate.
2. Provider test
No cleanup: the handler runs inside that interaction's own transaction, which is rolled back the moment the interaction finishes.
3. Publish and gate the deploy
pact:can-i-deploy fails closed. The broker answers true, false, or
null, and only true is treated as safe — an unverified contract blocks the
deploy exactly as a failing one does, because from the deployer's point of view
they are the same thing.
Attribute-driven test generation
Put the contract next to the code it describes:
writes a real file to tests/Pact/Generated/BillingClientFetchInvoicePactTest.php
— reviewable in a pull request, greppable, git blame-able, and named
individually in CI output. Field types become matcher calls, and sample
arguments are inferred from the signature and the contract path, so
fetchInvoice(42) matches /api/invoices/42 and the generated test actually
passes.
#[PactProvides] on a controller action generates provider-state handlers,
inferring factory calls from route-model-binding type hints and emitting an
explicit // TODO: define state setup where nothing can be inferred safely — a
wrong guess would produce a test that passes for the wrong reason.
Drift detection
Run this first in CI. It regenerates into memory, diffs against what is committed, and fails with a line-level diff — so a merged pull request can never contain an attribute that disagrees with the test claiming to enforce it.
Message and queue contracts
A lot of Laravel service-to-service traffic never touches HTTP.
On the provider side you register the code that produces the real thing:
The event or job is serialised through the framework's own path —
broadcastWith(), Arrayable::toArray(), or a job's public properties (queue
plumbing excluded, and reported as metadata instead). The output is a
spec-compliant V3 message pact, so a Node or Go service on the other end of
the queue can verify against it too.
Commands
| Command | What it does |
|---|---|
pact:generate-tests |
Write test files from #[PactConsumes] / #[PactProvides] |
pact:generate-tests --check |
Fail if any generated file is stale (run first in CI) |
pact:publish |
Publish pact files to the broker |
pact:verify |
Verify this provider against broker or local pacts |
pact:can-i-deploy |
Deploy gate; exits non-zero unless everything is verified |
A copy-pasteable pipeline wiring these in the right order lives in
docs/ci/github-actions.yml:
Matchers
Pact::match() exposes integer(), string(), email(), uuid(),
decimal(), boolean(), iso8601() and regex($example, $pattern), plus
like(), eachLike(), equal(), nullValue() and notEmpty(). Each maps to
a real Pact matching rule, never an exact-value assertion. Anything not
surfaced here falls through to pact-php's own matcher.
A trap worth knowing about.
decimal()against a whole number fails, because PHP encodes25.0as JSON25and Pact then sees an integer. Use a value with a fractional part in examples, ornumber()if the field really can be either.
Notes
- Parallel testing. No global mutable state: mock servers, sessions and
registries are bound per container, and the mock server binds port
0so Paratest workers cannot collide. - Failures are formatted. Both sides route through a formatter that names the interaction, the provider state that was active, the expected/actual diff, and a bounded excerpt of the real response body — an HTML error page is reduced to its title rather than dumping kilobytes of inlined CSS.
- Spec compliance is tested, not asserted.
tests/Feature/SpecComplianceTest.phpverifies a pact this package wrote using stock pact-php against a plain PHP provider, with nothing from laravel-pact involved.
Documentation
- Getting started — the full walkthrough
- CI pipeline — copy-pasteable GitHub Actions
Testing this package
Database-backed tests use SQLite in memory by default. To run them against a
real server — which also exercises real SAVEPOINT nesting:
Credits
Built on pact-foundation/pact-php,
which does all the hard protocol work.
License
MIT. See LICENSE.md.
All versions of laravel-pact with dependencies
ext-json Version *
illuminate/console Version ^11.0|^12.0|^13.0
illuminate/contracts Version ^11.0|^12.0|^13.0
illuminate/database Version ^11.0|^12.0|^13.0
illuminate/http Version ^11.0|^12.0|^13.0
illuminate/support Version ^11.0|^12.0|^13.0
pact-foundation/pact-php Version ^10.2
symfony/finder Version ^7.0|^8.0
symfony/process Version ^7.0|^8.0