Skip to content

Instantly share code, notes, and snippets.

@devster
Last active May 12, 2023 20:24
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save devster/ecb6f4701f5d516300b385fe83618b16 to your computer and use it in GitHub Desktop.
Save devster/ecb6f4701f5d516300b385fe83618b16 to your computer and use it in GitHub Desktop.
OpenApi symfony testing trait
<?php
namespace Tests\Infrastructure\Action\Channel;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Tests\TestCase\OpenApiTrait;
class ExampleTest extends WebTestCase
{
use OpenApiTrait;
public function testListing()
{
static::useOpenApiSchema('channel');
$client = static::createClient();
$client->request('GET', '/channels');
$this->assertOpenApi();
$response = $client->getResponse();
$this->assertTrue($response->isSuccessful());
}
}
<?php
declare(strict_types=1);
namespace Tests\TestCase;
use League\OpenAPIValidation\PSR7\OperationAddress;
use League\OpenAPIValidation\PSR7\SchemaFactory\YamlFileFactory;
use League\OpenAPIValidation\PSR7\ValidatorBuilder;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;
use Nyholm\Psr7\Factory\Psr17Factory;
use PHPUnit\Framework\AssertionFailedError;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
trait OpenApiTrait
{
public static array $openApiSpecFiles = [];
private static string $currentOpenApiFile;
private static array $validatorsBuilder = [];
private static PsrHttpFactory $psrHttpFactory;
private static KernelBrowser $client;
public static function useOpenApiSchema(string $name): void
{
static::$currentOpenApiFile = $name;
}
public function assertOpenApiRequest(Request $request): OperationAddress
{
try {
$psrRequest = static::createPsrRequest($request);
$op = static::getValidatorBuilder()->getRequestValidator()->validate($psrRequest);
$this->addToAssertionCount(1);
return $op;
} catch (\Throwable $e) {
throw static::createAssertionError('request', $e);
}
}
public function assertOpenApiResponse(OperationAddress $operationAddress, Response $response): void
{
try {
$psrResponse = static::createPsrResponse($response);
static::getValidatorBuilder()->getResponseValidator()->validate($operationAddress, $psrResponse);
$this->addToAssertionCount(1);
} catch (\Throwable $e) {
throw static::createAssertionError('response', $e);
}
}
public function assertOpenApi(KernelBrowser $client = null): void
{
$client = $client ?? static::$client;
$request = $client->getRequest();
$response = $client->getResponse();
$operationAddress = $this->assertOpenApiRequest($request);
$this->assertOpenApiResponse($operationAddress, $response);
}
/**
* Proxy to create a KernelBrowser and keep a reference of that client.
*
* @param array $options An array of options to pass to the createKernel method
* @param array $server An array of server parameters
*
* @return KernelBrowser A KernelBrowser instance
*/
protected static function createClient(array $options = [], array $server = [])
{
return static::$client = parent::createClient($options, $server);
}
private static function getValidatorBuilder(): ValidatorBuilder
{
$name = static::$currentOpenApiFile;
if (!array_key_exists($name, static::$validatorsBuilder)) {
$schemaFactory = new YamlFileFactory(static::$openApiSpecFiles[$name]);
$validatorBuilder = new ValidatorBuilder();
$validatorBuilder->fromSchema($schemaFactory->createSchema());
static::$validatorsBuilder[$name] = $validatorBuilder;
}
return static::$validatorsBuilder[$name];
}
private static function createPsrRequest(Request $request): ServerRequestInterface
{
return static::getPsrHttpFactory()->createRequest($request);
}
private static function createPsrResponse(Response $response): ResponseInterface
{
return static::getPsrHttpFactory()->createResponse($response);
}
private static function getPsrHttpFactory(): PsrHttpFactory
{
if (!isset(static::$psrHttpFactory)) {
$psr17Factory = new Psr17Factory();
static::$psrHttpFactory = new PsrHttpFactory($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory);
}
return static::$psrHttpFactory;
}
private static function createAssertionError(string $type, \Throwable $e): \Throwable
{
$message = "OpenApi [$type] validation error:";
$f = function (\Throwable $e) use (&$f) {
$message = "\n " . get_class($e) . ': ';
$message .= $e->getMessage();
if ($e instanceof SchemaMismatch && $bc = $e->dataBreadCrumb()) {
$message .= "\n Breadcrumbs: " . implode(' -> ', $bc->buildChain());
}
if ($p = $e->getPrevious()) {
$message .= $f($p);
}
return $message;
};
$message .= $f($e);
return new AssertionFailedError($message);
}
}
league/openapi-psr7-validator
nyholm/psr7
symfony/psr-http-message-bridge
<?php
require_once '../vendor/autoload.php';
use OpenApiTrait;
OpenApiTrait::$openApiSpecFiles = [
'channel' => __DIR__ . '/../documentation/channel/openapi.yaml',
];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment