Unit testing in Express with Promise-based Middleware and Controllers

Easier unit tests in ExpressJS by returning Promises from middleware.

Gustaf Holm
codeburst

--

A fat stack of callbacks are usually the way to use route middleware/controllers in good old Express tutorials around the web. It works fine but is usually a mess to unit test in my opinion 🙄. Especially when the middleware/controllers stack grows to include authentication, validation etc.

A note on Middleware, Controllers, Route handlers in Express

Express is unopinionated on how you structure flow control in routes. So words like controllers, middleware and route handlers might mean different things or for the most part kind of the same thing 😕, confusing I know. However, there are some great examples on how to setup your routes at Express GitHub repository.

☝️ The structure I use for unit testing in this tutorial can be applied to any middleware/controller/route handler in express. So don’t worry about that.

How routes usually are structured

The very very simple example show the “classic” way of setting up a route.

app.get('/endpoint', verifyToken, validateRequest, getThings);

In this structure verifyToken and validateRequest has to call next() at some point so that the request can move along the stack. This makes unit test on a specific middleware harder than it needs to be. So, many tutorials end up writing something more like a integration test for the whole route. With that kind of test you don’t have to figure out if a middleware calls next(). Of course it can be done with stuff like SinonJS but I think it makes things more complicated then they need to be. Especially when you add more routes and use the same middleware in multiple routes.

Middleware as Promises

A first step to get around this issue is to structuring my middleware/controllers around Promises. That way I can use async/await or .then().catch() on my controllers inside each route. In other words I control the calling of next() inside the route and not the controller.

Let’s look at an example. Here’s a over simplified example of the verifyToken controller.

exports.verifyToken = (req, res) => 
new Promise(async (resolve, reject) => {
const token = req.cookies.token;
if (token) {
try {
const user = await tokenFramework.verify(token, secret);
req.user = user;
resolve({ message: 'You have a valid token' });
} catch (error) {
reject({ message: 'Not a valid token', error });
}
};

And another example of the getThings controller.

exports.getThings = (req, res) => 
new Promise(async (resolve, reject) => {
try {
const things = await Thing.find({req.params.id});
resolve({ things });
} catch (error) {
reject({ message: 'Could not retrive things', error })
}
};

As you might figure out the main thing here is to return a Promise which will resolve() or reject() based on some logic inside the middleware/controller.

Handle Promises in the Route

As each middleware returns a promise I can just call then() on it and really do whatever I want. This is especially useful as it’s much clearer when next() is called and what happens throughout the middleware stack.

And of course, it makes unit testing specific middleware a breeze! ✌️

app.get('/endpoint',
(req, res, next){
verifyToken(req, res)
.then(() => next())
.catch(err => res.json(err))
},
(req, res, next){
validateRequest(req, res)
.then(() => next())
.catch(err => res.json(err))
},
(req, res, next){
getThings(req, res)
.then(response => res.json(response))
.catch(err => res.json(err))
};

If you don’t play nice with .then() you can use async/await together with try/catch like that 👇.

...
async (req, res, next) {
try {
const response = await validateRequest(req, res);
next();
} catch (error) {
res.json(error)};
},
...

Quick summery: Since I return a Promise from every controller middleware I moved the logic of calling next() to the route, very simple. If the promise resolves, call next() and proceed to the next middleware/controller or send the response to the client.

Run your tests

Now you just import the middleware in your test and the only thing you have to fake is the req and res . Use something like node-mocks-http for that.

Below I use tape for testing. I really like tape for it’s simplicity and it supports asynchronous testing in a clear simple way.

const test = require('tape');
const httpMocks = require('node-mocks-http');
const verifyToken = require('path/to/verifyToken');
test('verifyToken resolves with a success message', (assert) => {
const req = httpMocks.createRequest({
cookies: { token: 'blablabla' }
});
const res = httpMocks.createResponse();
return verifyToken(req, res).then((response) => {
...
assert.equal(typeof response.message, 'string');
...
});
});
test('getThings resolves to an array of objects', (assert) => {
const req = httpMocks.createRequest({
params: { id: '12345' }
});
const res = httpMocks.createResponse();
return getThings(req, res).then((response) => {
...
assert.equal(Array.isArray(response.things), 'true');
...
});
});

Since each controller now is self contained and only require a request and response as their arguments I can mock those and just call the function. No need to import your whole Express app or anything like that. Though if your working against a database, you have to connect to it in some way.

I’ve put together an app on GitHub that use Express, MongoDB and JSON Web tokens and has controllers/middleware to handle users, things (events in this case) and tokens. I hope you find it useful.

That’s it. Thanks for reading! 👋😎

--

--

Hej, I'm a developer. Hope you like what I write. You can also find me at twitter.com/primalivet 👋