I prefer to test outside-in – writing a high-level test that covers the use-case for a feature first, then working down to unit testing. When working on an API, this usually means writing HTTP tests that exercise endpoints from a consumer perspective.

I’ve yet to achieve the same elegance HTTP testing NodeJS as I have with frameworks like Laravel, which support it out-of-the-box. Supertest, however, empowers a similar workflow for Express.

Take an example express application that defines a single route /api/hello and returns a JSON response.

// server.js
const express = require('express');
const app = express();

app.get('/api/hello', (req, res) => {
    return res.status(200).json({ 
      message: 'Hello Test' 
    });
});

module.exports = app;

Imagine that we wanted to write a test that asserts the route responds with the correct message and status. In a test file, we can create a request using supertest that verfies that functionality:

// server.test.js
const request = require('supertest')
const app = require('./server')

describe('Hello route', () => {
  it('returns message', async () => {
    const res = await request(app)
      .get('/api/hello');

    expect(res.statusCode).toEqual(200);
    expect(res.body.message).toEqual('Hello Test');
  })
})

This quick example teases a lot of potential. With more time and effort and mocking, a robust set of tests that run alongside unit and integration tests may be possible. Such a test suite would enable the outside-in test approach.

Find the full code and package.json for this example on github.