In a previous post (the first in this series), I introduced the open-source OpenAPIValidators project that contains plugins to the Jest and Chai testing frameworks to ensure that an OpenAPI Specification matches the corresponding API implementation.
In this post, I want to demonstrate the OpenAPI Response Validator plugin by creating a small sample application. Hopefully, by the end, I'll have convinced you to add the Chai or Jest plugin to your toolkit so that your documentation always matches exactly how your API operates.
Demonstration
All the code for this tutorial is available on GitHub
Setup
- If you're starting from scratch run
npm init
, otherwise skip this step. - Install the required dependencies for your application. For the demo application I need Express -
npm install -s express
. - Next, install supertest, to make API calls in your tests, and either
chai
andchai-openapi-response-validator
orjest
andjest-openapi
depending on whether your preference (I'll be usingjest
).
In short copy one of these into your terminal:
# FOR CHAI
npm i -s supertest chai chai-openapi-response-validator
# OR
# FOR JEST
npm i -s supertest jest jest-openapi
Creating the application
You want to create two files:
-
server.js
- Where the API functions will live. The demo code contains a few different APIs to exhibit different OpenAPI definitions. Exporting the Express app enables it to be used alongsidesupertest
in the tests. -
app.js
- Requiresserver.js
and starts the server on a port.
server.js
should contain the following:
const express = require('express')
const app = express()
app.get('/hello', (req, res) => {
res.status(200).send('Hello World!')
});
app.post('/hello', (req, res) => {
res.sendStatus(202);
});
app.get('/customcode', (req, res) => {
res.status(418).send(`I'm a teapot`);
});
app.get('/object',(req, res) => {
res.status(200).send({
propertyShouldExist: true,
});
});
module.exports = app;
app.js
should contain the following:
const server = require('./server');
const port = 3000;
server.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
You can run this by node app.js
and open it in a browser on http://localhost:3000
.
Creating the OpenAPI Specification
Now that the API endpoints are created, we can create an OpenAPI Specification file (openapi.yml
) which describes how they should operate.
Create an openapi.yml
file with the following contents:
openapi: 3.0.3
info:
title: Open Api Validator Example
version: 0.0.1
paths:
/hello:
get:
responses:
200:
description: Response body should contain a string
content:
text/plain:
schema:
type: string
example: Hello World
post:
responses:
202:
description: Accepted
content:
text/plain:
schema:
type: string
example: Accepted
/customcode:
get:
responses:
418:
description: Response code I'm a teapot
content:
text/plain:
schema:
type: string
example: I'm a teapot
/object:
get:
responses:
200:
description: Response code I'm a teapot
content:
application/json:
schema:
type: object
required:
- propertyShouldExist
properties:
propertyShouldExist:
type: boolean
To understand the yaml above, take the first path/response definition:
paths:
/hello: // The API path
get: // The verb (get, post, put, delete etc)
responses: // An array of responses using the HTTP status code as a key
200: // A HTTP status code
description: Response body should contain a string
content: // What will be returned by the API
text/plain:
schema:
type: string
example: Hello World
By this point, you should have an Express server setup with a simple API and an openapi.yml
which can be used to determine how the API operates - what paths it contains and what it should return.
Writing the tests
Finally, let's write a test to assert that the Express API matches the OpenAPI Specification. This test will use the OpenAPI Specification explained above (GET request for /hello
).
Add the following to server.test.js
:
const jestOpenAPI = require('jest-openapi');
const request = require('supertest');
const path = require('path');
const server = require('./server');
// Sets the location of your OpenAPI Specification file
jestOpenAPI(path.join(__dirname, './openapi.yml'));
describe('server.js', () => {
it('should make a GET request and satisfy OpenAPI spec', async () => {
// Make request (supertest used here)
const res = await request(server).get('/hello');
// Make any assertions as normal
expect(res.status).toEqual(200);
// Assert that the HTTP response satisfies the OpenAPI spec
expect(res).toSatisfyApiSpec();
});
});
You'll also want to add:
"scripts": {
"test": "jest"
},
to your package.json
so that npm run test
will run your tests using Jest.
Running the tests
Run it with npm run test
.
The tests should pass the first time:
Now let's edit the Express app in server.js
to return a response that isn't documented in the OpenAPI Specification. We can do this by changing the /hello
GET request to return an object rather than a string.
app.get('/hello', (req, res) => {
res.status(200).send({ key: 'property' })
});
When you run the tests, they should fail as the actual response does not match the specification:
If you revert that change, you will see that the tests pass.
Finally, we can add more tests to the server.test.js
file to ensure we test each endpoint against the OpenAPI Specification. Change add the following into your existing describe
block.
it('should make a POST request and satisfy OpenAPI spec', async () => {
const res = await request(server).post('/hello');
expect(res.status).toEqual(202);
// Assert that the HTTP response satisfies the OpenAPI spec
expect(res).toSatisfyApiSpec();
});
it('should make a GET request, receive a 418 code and satisfy OpenAPI spec', async () => {
const res = await request(server).get('/customcode');
expect(res.status).toEqual(418);
// Assert that the HTTP response satisfies the OpenAPI spec
expect(res).toSatisfyApiSpec();
});
it('should make a GET request, receive an object and satisfy OpenAPI spec', async () => {
const res = await request(server).get('/object');
expect(res.status).toEqual(200);
// Assert that the HTTP response satisfies the OpenAPI spec
expect(res).toSatisfyApiSpec();
});
and your tests should output:
Congrats, having set up the above, if your OpenAPI specification becomes outdated your tests will fail and you'll know to update the documentation. In turn, you'll save a future developer time trying to use an API without matching documentation.
Summary
In this post, I've demonstrated the chai-openapi-response-validator and jest-openapi test packages which will improve your tests by checking your API responses against an OpenAPI Specification.
By using those packages you can ensure that your API always has an up-to-date OpenAPI Specification that can be consumed by Swagger.io and used by consumers of your API to ensure that they are making the correct API calls with the correct parameters - helping to remove frustration in the long run when documentation becomes outdated.
Any comments or questions let me know below. If you liked this article react or follow me!
Thanks for reading!
Top comments (4)
In my case I'm using OpenAPI 2 Spec but no matter how I try to make the GET or POST request I always get a 404 error instead of 200 or 201, I use swagger online editor and postman and the problem doesn't seen to be the services or endpoints. Not sure if it has to do with the fact that I'm using OpenAPI 2 instead of 3
Hi, the plugin should support OpenAPI 2 Spec. Here is a valid v2 spec that they use to test. It's possible that you've found a bug so I'd recommend raising it on GitHub.
In that example they specify the path in the json file, but in my case I'm doing it using express-openapi and I define the paths on each controller I create, could that be an issue at the moment of testing?
Thanks for the write up! Clear and easy to follow