Testing Express.js authorization middleware with mocking requests

When the application has private endpoints, which are not accessible by everyone, authorization middleware functions can be used to check whether the client has the necessary permissions. This logic should also be tested along with the rest of the API. By mocking the request object, we can easily test these functions.

Express.js, which is the most popular framework for Node.js, has a rich ecosystem for middleware functions.

1. Middleware functions

Middleware are functions, which are called before a route handler function is invoked. For example, if we have a private endpoint that provides access to content only available to subscribed clients, we’ll want to check if the user is authorized and authenticated when they type the URL in the browser.

For cases like this, we can create middleware functions, and all we have to do is just to call them before the route handler:

const express = require('express')

const app = express()

// ... code

app.get('/private', authorize, (req, res, next) => {
  // ... this will execute when the /private endpoint is hit
})

Lots of pre-build middleware libraries are available for Express, and we can also write our own ones. More information on middleware can be found on the Express.js website here and here. I also wrote a post on Express middleware, so I won’t deep dive on the topic here again.

2. Setup and code

The app I’ll use for this demonstration is the same as in this post. The full code can be downloaded from the GitHub repository of the project.

2.1. server.js

The relevant part of the code belongs to the /private endpoint. As it was stated above, private endpoints should not be accessed by anyone, and some sort of check is necessary to ensure that the user has the rights to get the content on the page.

The relevant code snippet has the following simple logic:

// server.js

app.get('/private', authorize, (req, res, next) => {
  res.send({ message: 'The private endpoint is only allowed for authenticated users' })
})

There’s nothing fancy here. We simply send an object with the message property in the response.

2.2. authorize middleware

The authorize middleware function performs the check to see if the user is allowed to access the content.

The function lives in its own module, and it looks like this:

// authorization.js

const AuthorizationError = require('./errors')

const authorize = (req, res, next) => {
  if (!(req.headers && req.headers.authorization)) {
    throw new AuthorizationError('Not authorized')
  }
  next()
}

module.exports = {
  authorize
}

It checks if the request header has the authorization property. If so, it calls the next function, which passes the control to the next function in the flow. In our case, this will be the route handler described in 2.1., which adds the object with the message property to the response.

If, however, the request doesn’t contain the authorization header, it means that the user is not allowed to access the private content, i.e. they are not logged in or they don’t have the necessary permissions. In this case, authorize will throw an error, and the execution will continue with the global error handler (in server.js).

2.3. AuthorizationError

authorize throws a custom error called AuthorizationError. Having a custom error is not compulsory at all and we could throw a regular Error instead. In fact, AuthorizationError itself inherits from the Error object:

// errors.js

class AuthorizationError extends Error {
  constructor(message) {
    super(message)
    this.name = 'AuthorizationError'
    this.httpStatusCode = 401
  }
}

module.exports = AuthorizationError

The advantage of having a custom AuthorizationError is that we can customize the error message, and decide what properties we want to add to the error object. In this case, it has a hardcoded httpStatusCode property and the name of the error.

3. mock-express-request

Now, we want to test the authorize middleware function.

authorize has two possible outcomes: either throws an error (if no authorization header is present), or calls next. Therefore, we will create two test cases, one for each scenario.

So we have to watch the req object (the client request) for the presence of the authorization header. Because the goal of the test is to see how the authorize function works, it’s a good idea to isolate it from the rest of the logic.

This means that we can mock the request object, and focus on the authorize middleware.

There is a great tool that mocks http requests called mock-express-request.

The library is very easy to use. All we have to do is to instantiate the MockExpressRequest class, and then add the required request properties: method, URL, headers, cookies etc.

The instance of the MockExpressRequest will replace the req (Request) object in the authorize middleware function.

Let’s see how!

4. Test cases

In order for the tests to work, we’ll need Mocha and Chai testing tools. Please refer to this post if you are unsure how to set them up in the project folder.

Now that the libraries are set up, we can have a look at the test cases themselves:

// authorization.spec.js

const { expect } = require('chai')
const MockExpressRequest = require('mock-express-request')
const sinon = require('sinon')

const { authorize } = require('../authorization')
const AuthorizationError = require('../errors')


describe('#authorize', () => {
  it('when authorization header is not present, it throws an error', () => {
    const request = new MockExpressRequest()

    expect(() => authorize(request)).to.throw(AuthorizationError)
  })

  it('does not throw error if the authorization header is set up', () => {
    const request = new MockExpressRequest({
      headers: {
        Authorization: '123qwe'
      }
    })
    const resSpy = sinon.spy()
    const nextSpy = sinon.spy()

    expect(() => authorize(request, resSpy, nextSpy)).to.not.throw()
    expect(nextSpy.called).to.be.true
  })
})

4.1. The sad path

In the first test case, we don’t provide the authorization header, and we expect that an AuthorizationError will be thrown.

request is the instance of the MockExpressRequest class, and this time we don’t need any arguments.

4.2. The happy path

The second test case is meant to examine the scenario when the header contains the authorization property. The argument of the MockExpressRequest instance is an object which has the headers property.

We can make use of the great spy, stub and mock library, Sinon, and create a spy on the response object and the next function.

In this case, we don’t expect any errors, but we expect that the next function (which is replaced by the nextSpy stub) to be called.

5. Conclusion

Authorization middleware functions are small but important parts of any applications that have endpoints with restricted access.

Testing these functions should be part of the test coverage of the application.

mock-express-request is one library that can be used to create mock requests, and it’s a useful part of our testing toolkit.

Thanks for reading and see you next time.