Using the OpenApiValidation Trait
The OpenApiValidation trait provides OpenAPI/Swagger validation functionality that can be used in any PHP class,
without requiring you to extend ApiTestCase.
Why Use the Trait?
Problem: Single Inheritance Limitation
PHP only allows single inheritance. If you have an existing test base class, you can't extend both:
// ❌ Can't do both!
class MyApiTest extends MyCompanyBaseTest // Your base class
class MyApiTest extends ApiTestCase // OpenAPI validation
// PHP doesn't support multiple inheritance
Solution: Use the Trait
The OpenApiValidation trait gives you all the OpenAPI validation functionality without inheritance constraints:
// ✅ Now you can have both!
class MyApiTest extends MyCompanyBaseTest
{
use OpenApiValidation;
// You now have access to all OpenAPI validation methods
}
Basic Usage
Option 1: Extend ApiTestCase (Traditional)
If you don't have a custom base class, just extend ApiTestCase:
<?php
use ByJG\ApiTools\ApiTestCase;
use ByJG\ApiTools\Base\Schema;
class MyApiTest extends ApiTestCase
{
public function setUp(): void
{
$schema = Schema::fromFile('/path/to/openapi.json');
$this->setSchema($schema);
}
public function testGetPet()
{
$request = new \ByJG\ApiTools\ApiRequester();
$request
->withMethod('GET')
->withPath('/pet/1');
$this->sendRequest($request);
}
}
Option 2: Use the Trait (Flexible)
If you have a custom base class, use the trait:
<?php
use ByJG\ApiTools\OpenApiValidation;
use ByJG\ApiTools\Base\Schema;
use ByJG\ApiTools\ApiRequester;
class MyApiTest extends MyCompanyBaseTest // Your existing base class
{
use OpenApiValidation; // Add OpenAPI validation
public function setUp(): void
{
parent::setUp(); // Call parent setup if needed
$schema = Schema::fromFile('/path/to/openapi.json');
$this->setSchema($schema);
}
public function testGetPet()
{
$request = new ApiRequester();
$request
->withMethod('GET')
->withPath('/pet/1');
$this->sendRequest($request);
}
}
Advanced Examples
Multiple Traits
You can combine multiple traits:
class MyApiTest extends TestCase
{
use OpenApiValidation; // OpenAPI validation
use DatabaseTransactions; // Laravel trait
use WithFaker; // Faker trait
public function testComplexScenario()
{
// Use database transactions
DB::beginTransaction();
// Use faker
$name = $this->faker->name;
// Use OpenAPI validation
$request = new ApiRequester();
$request
->withMethod('POST')
->withPath('/users')
->withRequestBody(['name' => $name])
->expectStatus(201);
$this->sendRequest($request);
DB::rollBack();
}
}
Custom Base Class with Utilities
// Your company's base test class
abstract class CompanyBaseTest extends TestCase
{
protected function authenticate(): string
{
// Company-specific authentication logic
return 'Bearer token123';
}
protected function getApiUrl(): string
{
return getenv('API_URL') ?: 'http://localhost:8080';
}
}
// Your API test using both
class UserApiTest extends CompanyBaseTest
{
use OpenApiValidation;
public function setUp(): void
{
parent::setUp();
$this->setSchema(Schema::fromFile(__DIR__ . '/openapi.json'));
}
public function testAuthenticatedRequest()
{
$request = new ApiRequester();
$request
->withMethod('GET')
->withPath('/user/profile')
->withRequestHeader([
'Authorization' => $this->authenticate() // Use parent method
])
->expectStatus(200);
$this->sendRequest($request);
}
}
Testing Framework Agnostic
While the trait works best with PHPUnit (for convenience methods), you can use it with other frameworks:
// Pest PHP example
use ByJG\ApiTools\OpenApiValidation;
use ByJG\ApiTools\Base\Schema;
use ByJG\ApiTools\ApiRequester;
uses(OpenApiValidation::class);
beforeEach(function () {
$this->setSchema(Schema::fromFile('openapi.json'));
});
test('get pet endpoint', function () {
$request = new ApiRequester();
$request
->withMethod('GET')
->withPath('/pet/1');
$this->sendRequest($request);
});
Available Methods
When you use the OpenApiValidation trait, you get these methods:
Configuration
// Set the OpenAPI schema to validate against
$this->setSchema(Schema $schema): void
// Set a custom requester (optional)
$this->setRequester(AbstractRequester $requester): void
// Get the current requester instance
$this->getRequester(): AbstractRequester|null
Request Validation
// Send and validate request (recommended)
$this->sendRequest(AbstractRequester $request): ResponseInterface
// Legacy method (deprecated, use sendRequest)
$this->assertRequest(AbstractRequester $request): ResponseInterface
Protected Helpers
// Check that schema is configured (throws exception if not)
$this->checkSchema(): void
Common Patterns
Per-Test Schema Configuration
class MultiApiTest extends TestCase
{
use OpenApiValidation;
public function testUserApi()
{
$this->setSchema(Schema::fromFile('user-api.json'));
$request = new ApiRequester();
$request->withMethod('GET')->withPath('/users');
$this->sendRequest($request);
}
public function testOrderApi()
{
$this->setSchema(Schema::fromFile('order-api.json'));
$request = new ApiRequester();
$request->withMethod('GET')->withPath('/orders');
$this->sendRequest($request);
}
}
Per-Request Schema (No setUp Required)
class FlexibleTest extends TestCase
{
use OpenApiValidation;
// No setUp method needed!
public function testWithInlineSchema()
{
$schema = Schema::fromFile('openapi.json');
$request = new ApiRequester();
$request
->withSchema($schema) // Schema on request
->withMethod('GET')
->withPath('/pet/1');
// No need to call setSchema() on test class
$this->sendRequest($request);
}
}
Migration from ApiTestCase
If you're currently extending ApiTestCase and want to switch to the trait:
Before:
class MyTest extends ApiTestCase
{
// ...
}
After:
class MyTest extends MyCustomBase // Or just TestCase
{
use OpenApiValidation;
// Everything else stays the same!
}
That's it! All your existing tests will work without any other changes.
PHPUnit Assertions
The trait automatically detects if it's being used in a PHPUnit TestCase and will execute PHPUnit assertions from convenience methods:
class MyTest extends TestCase // PHPUnit TestCase
{
use OpenApiValidation;
public function test()
{
$request = new ApiRequester();
$request
->withMethod('GET')
->withPath('/pet/1')
->expectStatus(200) // ✅ PHPUnit assertion will run
->expectJsonContains(['status' => 'available']); // ✅ Will run
$this->sendRequest($request);
}
}
If you're not using PHPUnit, the convenience methods will still validate against the OpenAPI schema, but won't register PHPUnit assertions.
Benefits
✅ Flexibility - Use with any base class
✅ Composition - Combine multiple traits
✅ Backward Compatible - ApiTestCase still works
✅ Clean Code - Separate concerns
✅ Testable - Easier to unit test
✅ Framework Agnostic - Works beyond PHPUnit
Summary
- Use
ApiTestCaseif you don't have a custom base class (simple) - Use
OpenApiValidationtrait if you have a custom base class (flexible) - Both provide the exact same functionality
- You can mix and match in the same project