API Endpoint Testing with Supertest - getfutureproof/fp_guides_wiki GitHub Wiki
When creating your own API, you'll want to add in some tests to check that your endpoints are responding in the way you intend when they receive a request.
Note that for testing code that makes requests, check out our Mocking Modules for Testing with Jest guide.
For this, we can make use of a library such as supertest. It can be used with any test runner. The examples below are done with Jest in mind but the majority of the code would be exactly the same if you were using eg. Mocha.
Install supertest
npm install -D supertest
Supertest is available as an npm library. The -D
flag is an alternative for --save-dev
and either is valid. We only need supertest during development so installing it as a dev dependency is appropriate here.
Require supertest
In the test file you wish to use it (or in a test setup file), require the library. You can name it anything you wish but the convention is to call it request
.
const request = require("supertest")
Let the tests see your server
We'll need access to our server so require it giving any name you like (here we've used 'server') and point it to a file which exports your server (in our case that's ../server
since the file is called server.js
and lives in the folder above)
const server = require("../server")
Note that to avoid the server running as soon as you require it (not desired!), it is best practice to export the server from where it is defined and import it into another file to be run at a suitable time. Your file structure may look something like this:
|- server
|- index.js # server is imported into here and started when this file is run
|- server.js # server is defined and exported here
|- /test
|- api.test.js # server is imported into here and starts with test run
Setup your tests
As with any test suite, you need to explain what you are testing and do any setup. In this case we are testing our API endpoints. Before we run the tests we will need to start up the API server and after them, we will need to stop it. In Jest we have the beforeAll
, beforeEach
, afterAll
and afterEach
hooks as options here. Other test runners will have similar options.
As we are now working with actions that take some time, we are also going to make use of an argument that Jest (and other test runners) will pass to callback functions. For convention's sake, we will call it done
. This done
argument is another function that, when invoked, indicates to the test runner that it is okay to move on. If you get a Timeout
error in your test suite, check that you have stated when done
should be run before looking into other solutions.
const request = require("supertest");
const server = require('../server');
describe('API server', () => {
let api
let testCat = {
"name": "Bob",
"age": 6
}
beforeAll(() => {
// start the server and store it in the api variable
api = server.listen(5000, () => console.log('Test server running on port 5000'))
})
afterAll(done => { // `done` always gets passed in but if we want to use it, we must name it!
// close the server, then run done
console.log('Gracefully stopping test server')
api.close(done) // `done` will be invoked when the `api.close` function is complete
})
})
Writing an endpoint test
To make a request to your api, you can use one of supertest's handy methods such as .get
to make a request and .expect
to make assertions on the response.
Testing a GET request
test('it responds to get /cats with status 200', done => { // we will want to call `done` when the test is complete so make sure you name it
request(api) // let supertest know the server it is requesting to (note we assigned our server to the variable `api` in the setup)
.get('/cats') // perform a get request to the endpoint of '/cats'
.expect(200, done) // assert that the response status will be 200 and pass `done` so expect can call it when it is... done!
})
Testing a POST request
A POST request usually has some data sent along with it. Supertest allows us to add that into the mix with the .send
method.
test('it responds to post /cats with status 201 and returns a new cat with an ID', done => {
let testCat = { "name": "Bob", "age": 6 } // Create some data to pass
request(api)
.post('/cats') // start a post request via supertest
.send(testCat) // send the testCat data as the request body
.expect(201) // assert that the response status will be 201
.expect({id: 4, ...testCat}, done) // assert that the response body will include an object with the passed data and an added ID
})
Testing other methods
Supertest provides support for a host of other methods including .patch
, .put
and .delete
. The implementation is as above.
Setting and checking headers
Whilst there are many more features of supertest, one that you might need to use more often is handling request headers.
To add a header to a request, use the .set
method:
test('it responds to post /cats with status 201 and returns a new cat with an ID', done => {
let testCat = { "name": "Bob", "age": 6 } // Create some data to pass
request(api)
.post('/cats') // start a post request via supertest
.send(testCat) // send the testCat data as the request body
.set('Content-Type', 'application/json') // add a header of 'Content-Type' with value 'application/json'
.expect(201) // assert that the response status will be 201
.expect({id: 4, ...testCat}, done) // assert that the response body will include an object with the passed data and an added ID
})
and to make an assertion on a response header:
test('it responds to get /cats with status 200', done => {
request(api)
.get('/cats')
.expect('Content-Type', 'application/json') // assert that the response will have header of 'Content-Type' with the value 'application/json'
.expect(200, done)
})