Pest plugin (Laravel)
Pest tests can use the same validator pipeline as PHPUnit through a custom-expectation plugin that ships in this package. The library runtime stays Pest-free — install Pest as a dev dependency in your project to activate the expectations.
- Installation
- Wiring the trait into Pest tests
expect(...)->toMatchOpenApiResponseSchema()expect(...)->toMatchOpenApiRequestSchema()- Constraints (v2)
Installation
composer require --dev pestphp/pest:^4.0The plugin is auto-loaded via Composer's autoload.files, so the expectations need no registration. The spec loader still does: register OpenApiCoverageExtension in phpunit.xml with a spec_base_path parameter, as shown in the Pest quickstart. Pest 4 uses PHPUnit 12; PHPUnit 13 remains supported by Gesso's PHPUnit integration but is not compatible with the Pest runner.
Wiring the trait into Pest tests
Mix ValidatesOpenApiSchema into the Pest test suite via uses(...)->in(...) in tests/Pest.php (or whatever Pest configuration file your project uses). The trait must be on the test class — typically through a base TestCase that already extends Laravel's testing harness:
// tests/Pest.php
use Studio\Gesso\Laravel\ValidatesOpenApiSchema;
use Tests\TestCase;
uses(TestCase::class, ValidatesOpenApiSchema::class)->in('Feature');Once wired, auto_assert and auto_validate_request work exactly as with PHPUnit — every Laravel HTTP helper call validates against the configured spec.
expect(...)->toMatchOpenApiResponseSchema()
Explicit response-side validation reads naturally inside Pest's expectation grammar:
it('lists pets with the documented shape', function () {
$response = $this->getJson('/api/v1/pets');
expect($response)->toMatchOpenApiResponseSchema();
});Optional named arguments cover the per-call overrides:
// Pin this single assertion to a different spec (overrides default_spec
// and any #[OpenApiSpec] on the test class for one call only).
expect($response)->toMatchOpenApiResponseSchema(spec: 'admin');
// Skip body validation for specific status codes (regex strings, anchored
// automatically). The standard `skip_response_codes` config still applies;
// these add to it for one call only.
expect($response)->toMatchOpenApiResponseSchema(skipResponseCodes: ['503']);
// Override method / path explicitly when the auto-resolved values from
// app('request') aren't what you want to validate against.
expect($response)->toMatchOpenApiResponseSchema(method: 'GET', path: '/api/v1/pets');The spec: override is single-shot: the next assertion in the same test method falls back to attribute / config resolution. Coverage recording, WeakMap dedup, and the #[SkipOpenApi] advisory warning all behave the same as the PHPUnit assertResponseMatchesOpenApiSchema() flow.
expect(...)->toMatchOpenApiRequestSchema()
Request-side validation accepts the Symfony Request Laravel hands out via app('request'):
it('accepts a documented request body shape', function () {
$this->postJson('/api/v1/pets', ['name' => 'Buddy']);
expect(app('request'))->toMatchOpenApiRequestSchema();
});The same spec: / method: / path: keyword arguments are accepted. The request bridge always runs (it bypasses the auto_validate_request config gate and #[SkipOpenApi]) because the explicit expectation reads as the user's direct intent. The response side warns when #[SkipOpenApi] and an explicit assertResponseMatchesOpenApiSchema() collide; the request side has no auto-vs-explicit advisory pattern to mirror, so silence on expect($request)->toMatchOpenApiRequestSchema() is the deliberate behaviour.
Constraints (v2)
- Laravel only. The expectations require the running Pest test class to use
ValidatesOpenApiSchema. Standalone (Symfony / framework-less) Pest support against PSR-7 messages is tracked as a follow-up to #109. - Pest 4 / PHPUnit 12. The Pest integration is tested on PHP 8.3, 8.4, and 8.5. Use the regular PHPUnit integration when running PHPUnit 13.
- Pest discovery contract. The plugin guards against a missing Pest install at autoload time, so it is safe to leave installed in projects that don't actually use Pest. If
pestphp/pestis absent, the bootstrap is a true no-op.