Recently, I've been adding some tests to an Express.js project . If you've never tested route methods, it might seem a little difficult, so let me show you how I'm doing it.

My routes look something like this:

app.post('/api/widget/',  
  hasPermission('create-widget'), 
  function (req, res, next) {
    WidgetLib
      .createWidget(req.body)
      .then(widget => res.json(widget))
      .catch(next);
    });

This route has two functions. The first is just a simple permission check, to see if the current user has permission to create a widget. If not, it will call the next function with the error as the parameter (calling next with a parameter immediately skip to error-handling middleware functions).

The WidgetLib.createWidget function will take the request body and return a promise. If the widget is successfully created, the promise resolves with the widget object, which we return to the client via res.json. If the creation fails, the promise will be rejected and we'll pass the error to the next function.

So, how can we test this? Do we have to write tests that actually run our Express app and send requests to our routes? We could do that, with Supertest, but I'm doing something different; I've taken the method that Jack Franklin wrote about and modified it a tiny bit. Since we want to do unit tests, we don't actually want to test our WidgetLib; so we'll be mocking our library and focusing on the route function itself.

Separating the Functions

The first step is to move the actual function outside of our express call. I've clustered route functions into files with related routes:

routes/widget.js
const hasPermission = require('./permission');  
const WidgetLib = require('../lib/widget');

module.exports = [  
  {
    route: '/',
    post: [
      hasPermission('create-widget'),
      (req, res, next) => {
        return WidgetLib
          .createWidget(req.body)
          .then(widget => res.json(widget))
          .catch(next);
      }
    ]
  }
];

A few things to note here:

First, notice that this module is an array, not an object. Often in Express, route order matters and arrays are ordered. An object would make it a little easier, because the routes can be the keys, but we need the order, so we use an array.

Second, each unique route has an object. The route key is the actual route; then, the HTTP verbs are used as key names for the methods. If multiple functions are needed (as with permissions middleware here), we put them in an array.

Third, notice that we're returning a promise from our route function. Express doesn't care about this; it's for our tests.

Finally, notice that instead of /api/widget/, I'm using just /. Keep this in mind for later.

Writing the Tests

Now that our function is separated from Express, it will be much easier to test. I'm using Tape for tests, but this will work with your testing library of choice as well. We're also going to use Proxyquire to stub the WidgetLib dependency and Sinon for other stubs.

Our test file begins thusly:

test/widgetRoutes.test.js
const test = require('tape');  
const sinon = require('sinon');  
const proxyquire = require('proxyquire');

let ACTION = 'resolve';  
let routes = proxyquire('../routes/widget', {  
  '../lib/widget': {
    createWidget: obj => Promise[ACTION]({})
  }
});

const getRoute = r => routes.filter(x => x.route === r)[0];

We pull in our libraries; we pull in our routes with proxyquire; the second parameter is an object that replaces libraries required from the library we are pulling in. This is where we stub WidgetLib. We're replacing WidgetLib.createWidget with a function that returns a promise. By changing the variable ACTION, we can choose whether this promise is successful or not.

Also, the getRoute function makes it easy to find a route in the routes array by the route name, instead of by index.

Now, for the actual test.

test('create widget route', t => {  
  let req = { body: {} };
  let res = {
    json: widget => t.ok(widget)
  };
  let next1 = sinon.spy();
  let next2 = sinon.spy();

  ACTION = 'resolve';
  getRoute('/')
    .post[1](req, res, next1)
    .then(() => {
      t.notOk(next1.called);

      ACTION = 'reject';
      getRoute('/')
        .post[1](req, res, next2)
        .then(() => {
          t.ok(next2.called);
          t.end();
        });
      });
});

This route function doesn't really do that much, so it's a very basic test. However, this will test both the success and failure paths of our route function, which is what's important.

Using the Routes

Our route functions are tested now, but since we pulled them our of Express, we're not using them. So let's put them back into Express. My preferred method of doing this is creating a Express router, and then using that in my Express app. If you haven't used Express routers, here's a simple example:

let app = express();  
let router = express.Router();

router.post('/', someFunction);

app.use('/api/widget', router);  

All the routes created in the route object will be prefixed with the route that is the first parameter to use: so / becomes /api/widget/ and /:id becomes /api/widget/:id.

This is why we used the route / instead of /api/widget/ in the route object; it makes it really easy to change this route later.

We have an array of routes that we want to convert to an Express router. Here's how I did it:

function toRouter(routes) {  
  let router = require('express').Router();

  routes.forEach(r => {
    Object.keys(r)
      .filter(verb => verb !== 'route')
      .forEach(verb => router[verb](r.route, r[verb]));
  });
  return router;
}

It's pretty easy: for each route, just filter out the route property and you're left with the HTTP verb keys, which we then apply to the router. Then, you can use it in your project:

let app = express();  
let widgetRoutes = require('./routes/widget');

app.use('/api/widget', toRouter(widgetRoutes));