November 16, 2016
Rich Gwozdz
STRUCTURING EXPRESS APPS TO FACILITATE TESTING

If you have worked with Express.js to build an API or application you will likely be familiar with the following boilerplate:


# index.js
 
var express = require('express');
var app = express();
 
app.get('/hello-world', function (req, res) {
  res.send('Hello World!');
});
 
app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});


Essentially, you instantiate the app, add a route to it, and have it “listen” for requests. Above, we are doing this all in one file. When I first started with Express, my app.js or index.js files often looked like this (though the route definitions were in a separate file). It works. However, when you want to “unit” or “integration” test your routes (meaning you actual hit the routes with test requests), it gets a little less simple. With the code above, you would actually have to start the app and get it listening on port 3000. Then your test-requests would presumably have to hit 127.0.0.1:3000/hello-world in order to get a response. Sounds okay, but leads to a few complications. You’ve got to litter your tests with references to 127.0.0.1:3000 or at least parameterize that fragment of the route-string. Also, you will have to make sure the app is “listening” prior to test execution – either manually or with some kind of “before” function that runs exactly once before all tests.

I guess this could work, but there is a better way, because you DON’T need to get your ExpressJS app “listening” to get requests to a given route. Instead, you leverage a NPM called supertest to target your app’s routes. However, you will need to setup your Express app differently than noted above:


# index.js
 
var express = require('express');
var app = express();
 
app.get('/hello-world', function (req, res) {
  res.send('Hello World!');
});
 
module.exports = app;


Notice we have removed the app.listen() and exported app via module.exports. Now create a second file called server.js that requires index.js.


# server.js
 
var app = require('./index.js');
 
app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});


When you want to start the app, you do it with server.js:

$ node server.js
Example app listening on port 3000!


BUT…when you want to test, you use index.js directly. Here’s an example test spec file (I use the mocha framework).


# testSpec.js
 
var supertest = require('supertest');
var app = require('index.js');
describe('GET /hello-world', function () {
  it('should have status 200 and contain specified data structure', function (done) {
 
    supertest(app)
      .get('/hello-world')
      .end(function (err, res) {
 
        // Test that the endpoint exists and responds
        expect(res).that.equals('Hello World!');
        done();
 
      });
  });
});


Notice we are able to access the application via require(‘index.js’) and then pass it to supertest. supertest then handles the specifics of the request/response chain, and allows you to focus on response assertions.

work
comments powered by Disqus