Gesso — OpenAPI 3.0/3.1/3.2 contract testing for PHP. Framework-independent core with Laravel, Symfony, Pest, and PSR-7 adapters. PHPUnit coverage, request/response validation, fuzzing, and drift detection.
studio-design/gesso is a Laravel package for gesso — openapi 3.0/3.1/3.2 contract testing for php. framework-independent core with laravel, symfony, pest, and psr-7 adapters. phpunit coverage, request/response validation, fuzzing, and drift detection..
It currently has 10 GitHub stars and 7.206 downloads on Packagist (latest version v2.6.0).
Install it with composer require studio-design/gesso.
Discover more Laravel packages by studio-design
or browse all Laravel packages to compare alternatives.
Last updated
/ˈdʒɛs.so/ — pronounced “JESS-so”
Gesso is the primer applied to a canvas before painting—a stable, receptive ground on which the finished work can be built. Gesso brings that same idea to APIs, providing a dependable foundation for OpenAPI contract testing in PHP.
Gesso provides framework-agnostic OpenAPI 3.0/3.1/3.2 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.
Gesso 2 is distributed as studio-design/gesso and declares its public PHP API
under Studio\Gesso\. Follow the staged v2 migration guide
when upgrading from studio-design/openapi-contract-testing v1.10.
Search the documentation · Core quickstart · Laravel · Symfony · Pest
QUERY, custom additionalOperations, form querystring, discriminator.defaultMapping, and observable streaming limitationsapplication/json and any +json content type(method, path, status, content-type) granularitygesso:routes finds documented operations without routes and registered routes without OpenAPI operations, with filters, stable JSON, and independent CI gatesignored_auth (does the API actually enforce declared security?), missing_required_header, and unsupported_method probes for the protocol-level holes schema validation cannot see, with deterministic replay and collect-then-assert reporting. See docs/fuzzing.md.enum: spec arrays, with PHPUnit-extension auto-discoverydocs/strict-required.md for current scope and limitations.properties / patternProperties when a schema silently defaults open. See docs/strict-additional-properties.md.5xx); per-request via skipResponseCode()pest --parallelConformance evidence: every example document the OpenAPI Initiative publishes for 3.0, 3.1, and 3.2 loads without error — 11 documents in both their JSON and YAML forms, of which ten are issue-free and tictactoe reports three security schemes as recognized but not enforced. The schema rewriting behind the claims above is pinned against the official JSON Schema Test Suite: of 3,784 cases compared, conversion changes the verdict on 11, all of them deliberate and individually documented. Both corpora are pinned by commit SHA and enforced in CI. See docs/conformance.md.
Choose based on the workflow you need rather than on a single yes/no feature count:
(method, path, status, content-type) granularity, several CI report formats, OpenAPI 3.1/3.2 JSON Schema semantics, schema-driven exploration, or drift detection across a framework-agnostic core and Laravel, Symfony, and Pest adapters.remoteSpecs.)| Capability | This library | Spectator v3.0.2 | league/psr7 v0.24 | osteel v0.14 | kirschbaum v2.0.2 |
| --- | --- | --- | --- | --- | --- |
| OpenAPI versions explicitly supported | 3.0, 3.1, 3.2 | Version scope not stated | 3.0.x | 3+; delegates to League v0.22 | Delegates to League v0.14–0.24 |
| Request + response validation | ✅ | ✅ Laravel | ✅ PSR-7 | ✅ HttpFoundation / PSR-7 | ✅ Laravel HTTP tests |
| Coverage granularity | method, path, status, content-type | method, path operation | — | — | — |
| Coverage outputs | Markdown, JUnit XML, JSON, HTML, GitHub Step Summary | Text, JSON | — | — | — |
| Parallel coverage merge | Sidecar + merge CLI | Not documented | — | — | — |
| Route/spec parity | gesso:routes with text/JSON and CI gates | spectator:routes | — | — | — |
| CLI diagnostics / scaffolding | doctor, gesso:routes, coverage merge/gate, stubs scoped to uncovered responses | validate, coverage, routes, stubs | — | — | — |
| Structured validation failures | issues() + versioned JSON failure output | JSON {errors: [...]} | PHP exception hierarchy | Wrapper exception | PHPUnit failure text |
| Schema-driven exploration | Deterministic endpoint + whole-spec generation | — | — | — | — |
| Drift / under-description checks | Enum drift, strict required | — | — | — | — |
| First-class integration | PSR-7, Laravel, Symfony, Pest | Laravel | PSR-7, PSR-15 middleware | HttpFoundation, PSR-7 | Laravel auto-validation |
| Declared runtime floor | PHP 8.3 core; Testbench 9–11 (Laravel 11–12; Laravel 13 / PHP 8.3) | PHP 8.3, Laravel 12 | PHP 7.2 | PHP 8.0, HttpFoundation 5–8 | PHP 8.0, Illuminate 10–13 |
Legend: ✅ supported · — no equivalent feature documented. “Not documented” is intentionally different from “unsupported”.
Methodology: This is a documentation/source audit, not a benchmark. Claims are limited to the linked, tag-pinned public documentation and Composer constraints checked on 2026-07-10. This-library claims describe main at 8c6416d; competitor versions are shown in the table header. Re-check this matrix using the release checklist at least quarterly or before a release when three months have elapsed.
$refscomposer require --dev "studio-design/gesso:^2.0"
YAML specs require
symfony/yaml. It is listed undersuggestso it isn't installed automatically. If your spec is JSON, you can skip this. If your spec is.yaml/.yml, add it explicitly:composer require --dev symfony/yamlWithout it, the loader throws
InvalidOpenApiSpecExceptionwith a clear "requires symfony/yaml" message the first time it tries to read a YAML file.
Choose the CI-tested five-minute path matching your stack:
| Stack | Passing example | What it demonstrates |
| --- | --- | --- |
| Framework-independent PHPUnit | examples/core | Direct response validation and coverage |
| Laravel | examples/laravel | Explicit assertion, auto_assert, and request validation |
| Laravel + Scramble | examples/scramble | Generated contracts, response drift, and untested 422 coverage gates |
| Symfony | examples/symfony | HttpFoundation request/response assertions |
| Pest | examples/pest | Laravel response and request expectations |
| PSR-7 | examples/psr7 | Request/response exchange validation |
All paths start with the same development dependency:
composer require --dev "studio-design/gesso:^2.0"
The example below uses a PSR-7 request and response. The searchable documentation contains the complete core, Laravel, Symfony, and Pest quickstarts.
Point the loader at your spec's entry file. Internal and local-filesystem $ref are resolved automatically — no pre-bundling required:
openapi/
├── root.yaml # paths reference ./schemas/*.yaml
└── schemas/
├── pet.yaml
└── error.json
Before running your first test, verify that the package can load and enforce the contract:
vendor/bin/gesso doctor \
--spec=openapi/root.yaml \
--strip-prefix=/api \
--phpunit-snippet
The command resolves local references, checks the OpenAPI/JSON Schema dialect, reports unsupported enforcement features, counts discovered operations and responses, and exits non-zero for incompatible specs. Use --format=json in CI. See the doctor command reference for multiple specs, HTTP references, output categories, and exit codes.
Then register the emitted configuration:
<extensions>
<bootstrap class="Studio\Gesso\PHPUnit\OpenApiCoverageExtension">
<parameter name="spec_base_path" value="openapi/bundled"/>
<parameter name="strip_prefixes" value="/api"/>
<parameter name="specs" value="front,admin"/>
</bootstrap>
</extensions>
When your application or HTTP client already returns PSR-7 messages, validate both sides and record coverage with one framework-independent call:
use Studio\Gesso\Psr7\OpenApiPsr7Validator;
$validator = new OpenApiPsr7Validator('front');
$result = $validator->validateExchange($request, $response);
$this->assertTrue($result->isValid(), $result->errorMessage());
The adapter accepts any psr/http-message implementation; no concrete PSR-7
package is added to production dependencies. A PHPUnit assertion trait,
response-only operation addressing, PSR-15 test recipe, and stream guarantees
are covered in the PSR-7 guide.
php artisan vendor:publish --tag=gesso
Set default_spec in the published config/gesso.php, then mix in the trait:
use Studio\Gesso\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);
}
}
Before running tests, compare Laravel's registered routes with the spec:
php artisan gesso:routes --fail-on-undocumented --fail-on-unimplemented
To validate every response automatically, set 'auto_assert' => true and drop the explicit assert call. To also catch request-side drift, set 'auto_validate_request' => true. See docs/setup.md for the full configuration and opt-out reference.
| Topic | Reference |
|---|---|
| PSR-7 request / response / exchange validation and PSR-15 test recipe | docs/psr7.md |
| Full setup, Laravel / Symfony / framework-agnostic adapters, auto-assert, opt-out attributes, request validation, HTTP $ref | docs/setup.md |
| Pre-test compatibility diagnostics (gesso doctor) | docs/doctor.md |
| Laravel route/spec parity (gesso:routes) | docs/laravel-route-parity.md |
| Pest plugin: expect()->toMatchOpenApiResponseSchema() and friends | docs/pest-plugin.md |
| Schema-driven request fuzzing & named contract checks | docs/fuzzing.md |
| Enum drift detection | docs/enum-drift.md |
| Schema under-description detection (strict_required) | docs/strict-required.md |
| Undocumented response-property detection (strict_additional_properties) | docs/strict-additional-properties.md |
| Violation baseline: adopt on a legacy API, fail only on new violations | docs/baseline.md |
| Coverage baseline: gate the set of uncovered responses instead of a percentage | docs/coverage-baseline.md |
| Coverage report modes & threshold gate | docs/coverage.md |
| Spec patch coverage: fail a PR that changes an operation no test covers | docs/coverage-gate.md |
| Test stub scaffolding for the responses no test covers | docs/stubs.md |
| HTML coverage output | docs/coverage-html-output.md |
| JSON coverage output schema | docs/coverage-json-schema.md |
| JSON validation result output schema | docs/validation-json-schema.md |
| Parallel test runners (paratest / Pest --parallel) | docs/parallel.md |
| CI integration (GitHub Actions, PR comments, output formats, partial-run handling) | docs/ci.md |
| API reference (OpenApiResponseValidator, OpenApiSpecLoader, OpenApiCoverageTracker) | docs/api-reference.md |
| Supported features, known limitations, warning channel | docs/supported-features.md |
| Conformance results (official OAS example documents, JSON Schema conversion delta) | docs/conformance.md |
| Versioning policy & support matrix | docs/versioning.md |
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
MIT License. See LICENSE for details.