studio-design / openapi-contract-testing
Framework-agnostic OpenAPI 3.0/3.1 contract testing for PHPUnit with endpoint coverage tracking
Installs: 59
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/studio-design/openapi-contract-testing
Requires
- php: ^8.2
- opis/json-schema: ^2.6
- phpunit/phpunit: ^11.0 || ^12.0 || ^13.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.94
- illuminate/testing: ^11.0 || ^12.0
- phpstan/phpstan: ^2.0
Suggests
- illuminate/testing: Required for the Laravel adapter (ValidatesOpenApiSchema trait)
- dev-main
- v0.7.0
- v0.6.0
- v0.5.2
- v0.5.1
- v0.5.0
- v0.4.0
- v0.3.0
- v0.2.0
- v0.1.0
- dev-docs/readme-content-negotiation
- dev-feat/content-negotiation-support
- dev-fix/non-json-content-type-regression
- dev-fix/json-compatible-content-type-validation
- dev-chore/add-renovate-config
- dev-fix/non-json-response-handling
- dev-docs/readme-config-based-default-spec
- dev-feat/config-based-default-spec
- dev-docs/clarify-dereferenced-flag
- dev-fix/replace-openapi-spec-property-with-abstract-method
- dev-fix/empty-body-response-crash
- dev-renovate/configure
- dev-feat/ci-markdown-coverage-output
- dev-feat/support-php84-phpunit13
- dev-fix/ci-matrix-php82-phpunit12
This package is auto-updated.
Last update: 2026-02-21 15:00:47 UTC
README
Framework-agnostic OpenAPI 3.0/3.1 contract testing for PHPUnit with endpoint coverage tracking.
Validate your API responses against your OpenAPI specification during testing, and get a coverage report showing which endpoints have been tested.
Features
- OpenAPI 3.0 & 3.1 support — Automatic version detection from the
openapifield - Response validation — Validates response bodies against JSON Schema (Draft 07 via opis/json-schema). Supports
application/jsonand any+jsoncontent type (e.g.,application/problem+json) - Content negotiation — Accepts the actual response
Content-Typeto handle mixed-content specs. Non-JSON responses (e.g.,text/html,application/xml) are verified for spec presence without body validation; JSON-compatible responses are fully schema-validated - Endpoint coverage tracking — Unique PHPUnit extension that reports which spec endpoints are covered by tests
- Path matching — Handles parameterized paths (
/pets/{petId}) with configurable prefix stripping - Laravel adapter — Optional trait for seamless integration with Laravel's
TestResponse - Zero runtime overhead — Only used in test suites
Requirements
- PHP 8.2+
- PHPUnit 11, 12, or 13
- Redocly CLI (recommended for
$refresolution / bundling)
Installation
composer require --dev studio-design/openapi-contract-testing
Setup
1. Bundle your OpenAPI spec
This package expects a bundled (all $refs resolved) JSON spec file. Use Redocly CLI to bundle:
npx @redocly/cli bundle openapi/root.yaml --dereferenced -o openapi/bundled/front.json
Important: The
--dereferencedflag is required. Without it,$refpointers (e.g.,#/components/schemas/...) are preserved in the output, causingUnresolvedReferenceExceptionat validation time. The underlying JSON Schema validator (opis/json-schema) does not resolve OpenAPI$refreferences.
2. Configure PHPUnit extension
Add the coverage extension to your phpunit.xml:
<extensions> <bootstrap class="Studio\OpenApiContractTesting\PHPUnit\OpenApiCoverageExtension"> <parameter name="spec_base_path" value="openapi/bundled"/> <parameter name="strip_prefixes" value="/api"/> <parameter name="specs" value="front,admin"/> </bootstrap> </extensions>
| Parameter | Required | Default | Description |
|---|---|---|---|
spec_base_path |
Yes* | — | Path to bundled spec directory (relative paths resolve from getcwd()) |
strip_prefixes |
No | [] |
Comma-separated prefixes to strip from request paths (e.g., /api) |
specs |
No | front |
Comma-separated spec names for coverage tracking |
output_file |
No | — | File path to write Markdown coverage report (relative paths resolve from getcwd()) |
*Not required if you call OpenApiSpecLoader::configure() manually.
3. Use in tests
With Laravel (recommended)
Publish the config file:
php artisan vendor:publish --tag=openapi-contract-testing
This creates config/openapi-contract-testing.php:
return [ 'default_spec' => '', // e.g., 'front' ];
Set default_spec to your spec name, then use the trait — no per-class override needed:
use Studio\OpenApiContractTesting\Laravel\ValidatesOpenApiSchema; class GetPetsTest extends TestCase { use ValidatesOpenApiSchema; public function test_list_pets(): void { $response = $this->get('/api/v1/pets'); $response->assertOk(); $this->assertResponseMatchesOpenApiSchema($response); } }
To use a different spec for a specific test class, override openApiSpec():
class AdminGetUsersTest extends TestCase { use ValidatesOpenApiSchema; protected function openApiSpec(): string { return 'admin'; } // ... }
Framework-agnostic
use Studio\OpenApiContractTesting\OpenApiResponseValidator; use Studio\OpenApiContractTesting\OpenApiSpecLoader; // Configure once (e.g., in bootstrap) OpenApiSpecLoader::configure(__DIR__ . '/openapi/bundled', ['/api']); // In your test $validator = new OpenApiResponseValidator(); $result = $validator->validate( specName: 'front', method: 'GET', requestPath: '/api/v1/pets', statusCode: 200, responseBody: $decodedJsonBody, responseContentType: 'application/json', // optional: enables content negotiation ); $this->assertTrue($result->isValid(), $result->errorMessage());
Coverage Report
After running tests, the PHPUnit extension prints a coverage report:
OpenAPI Contract Test Coverage
==================================================
[front] 12/45 endpoints (26.7%)
--------------------------------------------------
Covered:
✓ GET /v1/pets
✓ POST /v1/pets
✓ GET /v1/pets/{petId}
✓ DELETE /v1/pets/{petId}
Uncovered: 41 endpoints
CI Integration
GitHub Actions Step Summary
When running in GitHub Actions, the extension automatically detects the GITHUB_STEP_SUMMARY environment variable and appends a Markdown coverage report to the job summary. No configuration needed.
Note: Both features are independent — when running in GitHub Actions with
output_fileconfigured, the Markdown report is written to both the file and the Step Summary.
Markdown output file
Use the output_file parameter to write a Markdown report to a file. This is useful for posting coverage as a PR comment:
<extensions> <bootstrap class="Studio\OpenApiContractTesting\PHPUnit\OpenApiCoverageExtension"> <parameter name="spec_base_path" value="openapi/bundled"/> <parameter name="specs" value="front,admin"/> <parameter name="output_file" value="coverage-report.md"/> </bootstrap> </extensions>
Example GitHub Actions workflow step to post the report as a PR comment:
- name: Run tests run: vendor/bin/phpunit - name: Post coverage comment if: github.event_name == 'pull_request' && hashFiles('coverage-report.md') != '' uses: marocchino/sticky-pull-request-comment@v2 with: path: coverage-report.md
OpenAPI 3.0 vs 3.1
The package auto-detects the OAS version from the openapi field and handles schema conversion accordingly:
| Feature | 3.0 handling | 3.1 handling |
|---|---|---|
nullable: true |
Converted to type array ["string", "null"] |
Not applicable (uses type arrays natively) |
prefixItems |
N/A | Converted to items array (Draft 07 tuple) |
$dynamicRef / $dynamicAnchor |
N/A | Removed (not in Draft 07) |
examples (array) |
N/A | Removed (OAS extension) |
readOnly / writeOnly |
Removed (OAS-only in 3.0) | Preserved (valid in Draft 07) |
API Reference
OpenApiResponseValidator
Main validator class. Validates a response body against the spec.
The optional responseContentType parameter enables content negotiation: when provided, non-JSON content types (e.g., text/html) are checked for spec presence only, while JSON-compatible types proceed to full schema validation.
$result = $validator->validate( specName: 'front', method: 'GET', requestPath: '/api/v1/pets/123', statusCode: 200, responseBody: ['id' => 123, 'name' => 'Fido'], responseContentType: 'application/json', ); $result->isValid(); // bool $result->errors(); // string[] $result->errorMessage(); // string (joined errors) $result->matchedPath(); // ?string (e.g., '/v1/pets/{petId}')
OpenApiSpecLoader
Manages spec loading and configuration.
OpenApiSpecLoader::configure('/path/to/bundled/specs', ['/api']); $spec = OpenApiSpecLoader::load('front'); OpenApiSpecLoader::reset(); // For testing
OpenApiCoverageTracker
Tracks which endpoints have been validated.
OpenApiCoverageTracker::record('front', 'GET', '/v1/pets'); $coverage = OpenApiCoverageTracker::computeCoverage('front'); // ['covered' => [...], 'uncovered' => [...], 'total' => 45, 'coveredCount' => 12]
Development
composer install # Run tests vendor/bin/phpunit # Static analysis vendor/bin/phpstan analyse # Code style vendor/bin/php-cs-fixer fix vendor/bin/php-cs-fixer fix --dry-run --diff # Check only
License
MIT License. See LICENSE for details.