Testing Express APIs with Tape and Supertest

Our tech world is an ever changing ecosystem where is hard to stay up to date and keep with all the changes. But it’s always nice to add new things to the toolbox, so since the moment I read Eric Elliot‘s post Why I use Tape Instead of Mocha & So Should You back in summer I wanted to try it.

Three weeks ago I got a project to create an Express API with Redis as the database and I felt it was the perfect moment to try the combination of Tape and Supertest. So I did use it and I have to say I’m surprised by the speed and simplicity.

Express API

I’m not going to talk much about setting our API because there are hundreds of good examples on the Internet. But I wan to point out two key points for this testing set-up to work.

The first one is well known. As Supertest expects a function we need to expose our Express app so we can reuse it in our tests.

var express = require('express');
var app = express();
exports = module.exports = app;

But the second one took me a while to figure out and when I saw I’m not the only one having the same issue, I decided to write this blog post.

Tape was running the tests properly but even when I was explicitly finishing all of them with the end() method, the process was hanging indefinitely. This happened because I had two open handles, the http server listening for connections and the connection to the Redis server.

To listen for connections only when our index.js file is called directly by node and not when is required in our tests we need to access the main module.

if (require.main === module) {
  app.listen(3000);
}

And to close the socket connection with the Redis server when no more commands are pending we have the unref() method.

if (process.env.NODE_ENV === 'test') {
  redisClient.unref();
}

With these little tweaks our index.js file should look similar to this one.

 'use strict'; 

// Set default node environment to development 
process.env.NODE_ENV = process.env.NODE_ENV || 'development'; 

var express = require('express'); 
var config = require('./config/environment'); 

// Connect to database 
var redisClient = require('redis').createClient(); 
if (process.env.NODE_ENV === 'test') {   
  redisClient.unref(); 
} 

// Setup server 
var app = express(); 
var server = require('http').createServer(app); 
require('./routes')(app); 

// Start server 
if (require.main === module) {   
  server.listen(config.port, config.ip, function () {
    console.log('Express server listening on %d, in %s mode', config.port, app.get('env'));
  }); 
} 

// Expose app 
exports = module.exports = app; 

And now when we run the tape process it will finish outputting the number of running and passing tests.

API testing

Once we have our API ready to work with, setting up the specs is super easy as we just need to require Tape, Supertest and our Express app.

'use strict';

var test = require('tape');
var request = require('supertest');

var app = require('../app/index.js');

For testing a GET request we can call the endpoint and check that what we are actually getting as the response is what we expected to get.

test('GET /things', function (assert) {
  request(app)
    .get('/things')
    .expect(200)
    .expect('Content-Type', /json/)
    .end(function (err, res) {
      var expectedThings = [
        { id: 1, name: 'One thing' },
        { id: 2, name: 'Another thing' }
      ];
      var actualThings = res.body;

      assert.error(err, 'No error');
      assert.same(actualThings, expectedThings, 'Retrieve list of things');
      assert.end();
    });
});

And for a POST we can create a new object and send it to the API checking again the expected response.

test('POST /things', function (assert) {
  var newThing = { id: 3, name: 'New Thing'};
  request(app)
    .post('/things')
    .send(newThing)
    .expect(201)
    .expect('Content-Type', /json/)
    .end(function (err, res) {
      var actualThing = res.body;

      assert.error(err, 'No error');
      assert.same(actualThing, newThing, 'Create a new thing');
      assert.end();
    });
});

These are just really simple examples to showcase the endless possibilities, but Test Driven Development is really possible with this approach avoiding the need of running our API to check every new method.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s