How to use the Open API and Swagger Validator plugin for Chai and Jest to keep your API documentation up to date
Tags: javascript, testing, tutorial, webdev
Published: 2021-01-18 14:11:53

[In a previous post (the first in this series)](https://dev.to/jameswallis/ensuring-your-openapi-swagger-specification-is-never-outdated-through-testing-4h9c), I introduced the [open-source OpenAPIValidators project](https://github.com/openapi-library/OpenAPIValidators) that contains plugins to the Jest and Chai testing frameworks to ensure that an OpenAPI Specification matches the corresponding API implementation.

<img width="50%" style="width:50%" src="https://media.giphy.com/media/kD6GZ1VzBFe7ToI5q6/giphy.gif">

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](https://github.com/james-wallis/OpenAPIValidatorExamples)*
### Setup

1. If you're starting from scratch run `npm init`, otherwise skip this step.
1. Install the required dependencies for your application. For the demo application I need [Express](https://expressjs.com/) - `npm install -s express`.
1. Next, install [supertest](https://www.npmjs.com/package/supertest), to make API calls in your tests, and either `chai` and `chai-openapi-response-validator` or `jest` and `jest-openapi` depending on whether your preference (I'll be using `jest`).

In short copy one of these into your terminal:
```bash
# 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:
1. `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 alongside `supertest` in the tests.
2. `app.js` - Requires `server.js` and starts the server on a port.

**`server.js` should contain the following:**

```javascript
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:**

```javascript
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:
```yaml
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:
```yaml
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`:

```javascript
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:
```json
"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:

![Passing demo test](https://dev-to-uploads.s3.amazonaws.com/i/5r2i7vvsvjanhwab59oq.png)

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.

```javascript
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:

![Failing demo test](https://dev-to-uploads.s3.amazonaws.com/i/gpflhp1s5zzr7pkwg1dn.png)

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.

```javascript
  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:

![All tests passing](https://dev-to-uploads.s3.amazonaws.com/i/qao7157kit830r1uu0ec.png)

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.

<img width="100%" style="width:100%" src="https://media.giphy.com/media/xUySTUZ8A2RJBQitEc/giphy.gif">

## Summary

In this post, I've demonstrated the [chai-openapi-response-validator](https://www.npmjs.com/package/chai-openapi-response-validator) and [jest-openapi](https://www.npmjs.com/package/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!