Raven - How to test your API documentation and behavior.
This library was written to allow testing OpenAPI documentation easily. It also allows verifying that your code implementation is compatible with that documentation.
Why creating such tool ?
We work a lot on API related projects. Sometimes we create API, sometimes we consume them. The OpenAPI description format is now well known and used in a lot of different contexts.
Our concern is that it's hard to ensure that the written documentation is representing the current API behavior. We wanted to track the implementation difference between the doc and the code.
We searched the ecosystem and found that it exists different tools to mock APIs or to perform requests to them. However we can't find a tool that allows performing HTTP requests that use fixtures and are able to perform specific validation on the responses.
So we started working on Raven
!
It relies on PSRs to be easily integrated in any project and is composed of two different parts:
- An HTTP request factory to define the input,
- An executor that'll be responsible to actually validate Requests and Responses.
Raven, isn't it a bird ?
Nope, we use here the human name of the X-Men character Mystique. She's able to transform and adapt to any situation which is that tool goal. We need to adapt to any API to trigger valid requests and analyze responses.
Install it
Using Composer:
composer require chstudio/raven
To use Raven you might need to also install:
Of course you can also write your own. The only constraint is to be compatible with PSRs interfaces.
Usage
Execute Request / Response validation
This library defines its own interfaces for request validation. It comes with an adapter to the league/openapi-psr7-validator package which define a complete validation logic.
<?php
use CHStudio\Raven\Bridge\LeagueOpenAPIValidation\Factory;
use CHStudio\Raven\Validator\Expectation\ExpectationCollection;
// Load OpenAPI description file
$factory = Factory::fromYamlFile('specific/path/to/openapi.yaml');
$executor = new Executor(
/** Your own HTTP client implementation */,
$factory->getRequestValidator(),
$factory->getResponseValidator()
);
$executor->execute($request);
Generate requests easily based on configuration
Writing RequestInterface
objects manually might not be the simplest way to define your test cases. We created a RequestFactory
to help building those objects. It rely on PSR17 HTTP factories.
Here is an example which use the nyholm/psr7.
<?php
use CHStudio\Raven\Http\Factory\RequestFactory;
use Nyholm\Psr7\Factory\Psr17Factory;
$psrFactory = new Psr17Factory();
$requestFactory = new RequestFactory($psrFactory, $psrFactory);
$request = $requestFactory->fromArray([
'uri' => 'http://myhost.com/api/users/me',
'method' => 'POST',
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer token'
],
'body' => '{"aSimple": "objectDefinition", "yep": true}'
]);
If the body is given as an array, it will be encoded based on the Content-Type
header:
application/json
, no header or unsupported header will be transformed to JSON,multipart/form-data
, will usehttp_build_query
.
Enrich your request body with resolver
Most of the time having static request bodies will not be powerful enough. We need identifiers and other details extracted from our fixtures. A specific layer can be added around the RequestFactory
to resolve body dynamically.
You can combine different Resolver
and let the configured body pass through all the methods and be enriched. This library come with a specific Faker
resolver to generate data easily with providers (see Faker doc).
You can build your own resolvers using the ValueResolverInterface
.
<?php
use CHStudio\Raven\Http\Factory\Resolver\ArrayValueResolver;
use CHStudio\Raven\Http\Factory\Resolver\FakerValueResolver;
use CHStudio\Raven\Http\Factory\Resolver\PassThroughValueResolver;
//Configure your own faker generator, maybe with some `addProvider` calls.
$generator = \Faker\Factory::create();
//Configure specific resolver logic.
$valueResolver = new ArrayValueResolver(
new FakerValueResolver(
$generator,
new PassThroughValueResolver()
)
);
//Apply it on the request factory built in the previous section.
$requestFactory = new RequestUriParametersResolver(
$valueResolver,
new RequestBodyResolver(
$valueResolver,
$requestFactory
)
);
$request = $requestFactory->fromArray([
'uri' => [
'base' => 'http://myhost.com/api/users/{id}',
'parameters' => [
//This value will be resolved by `RequestUriParametersResolver`
'{id}' => '<userId()>'
]
],
'method' => 'POST',
'body' => [
'scalar' => [
'bool' => true,
'int' => 23456,
'float' => 18.06
],
//Built in Faker provider resolved by `RequestBodyResolver`
'faker' => [
'name' => '<name()>',
'creationDate' => '<date("Y-m-d")>',
]
//Specific provider to query database
'institution' => '<institutionId("Massachusetts General Hospital")>'
]
]);
/**
* This will generate an URL like this: http://myhost.com/api/users/a5098711-b6b2-4acb-96ea-f8baf496c700
*
* This will generate the following body:
*
* {
* "scalar": {
* "bool": true,
* "int": 23456,
* "float": 18.06
* },
* "faker": {
* "name": "John Doe",
* "creationDate": "2022-10-03"
* },
* "institution": "bf91c434-dcf3-3a4c-b49a-12e0944ef1e2"
* }
*/
Custom expectations
Validating that the request and the response are respecting the documentation is nice but we might need to add some user defined expectations. Will this request trigger a 401 response ? Is the body containing the correct value ?
Expectation can be built using request definition data. Based on some properties, they will be added dynamically. The expectation collection can be passed to the Executor::execute
method.
If one of the expectation fails, the response validation will fail and you'll get the details through a ExpectationFailedException
error.
<?php
use CHStudio\Raven\Validator\Expectation\ExpectationFactory;
$requestData = [
'uri' => 'http://myhost.com/api/users/me',
'method' => 'GET',
'statusCode' => 403
];
$expectations = (new ExpectationFactory())->fromArray($requestData);
$request = $requestFactory->fromArray($requestData);
$executor->execute($request, $expectations);
This library come with built in expectations: StatusCode
. You can easily build your own using the ResponseExpectationInterface
.
License
This package is released under the Apache-2 license.
Contribute
If you wish to contribute to the project, please read the CONTRIBUTING notes.