Faking errors to test error scenarios in Express API's

You've written tests for your Express app.

You've got most "happy path" test cases covered. Under normal circumstances, your API works as expected.

But now you need to write a test for how your API handles an error. You want to test that your API returns a HTTP 500 status code, for example, if there is an internal server error.

The problem is... under normal circumstances your code doesn't encounter an error scenario...

So, how do you trigger one so you can write that test and get on with writing actual app code? Instead of spinning your wheels figuring out how to write the test code!

This is where stubs come into play. Let's go over that now.

Faking an error

You might have heard the terms spy, stub, or mock before. We'll call these collectively fakes.

The specific fake we want to use here is a stub - this will allow us to override the function we want to trigger an error for, so we can test our Express response status.

In this case, let's say we want to test that our REST API returns a 500 error code when called.

Let's imagine we have a route /api/search, that makes a call to a database. We want to see what happens when that database call throws an error. When that "bubbles up" to the Express route, what is returned by Express?

In our app the flow of code goes HTTP request ---> Express route ---> Controller ---> Service ---> Database

Our database code looks like this:

const search = async (term, numToFetch = null) => {
  return db.select('*').from('item').where('name', 'like', `%${term}%`).limit(numToFetch)
}

export {
  search
}

search gets called by the service, which is called by the controller, which is called by the route.

Sinon to the rescue

So how do we actually use a stub to fake an error?

This is where sinon and its stubbing ability comes to the rescue.

We can "fake" an error using sinon by doing something like:

sinon.stub(module, 'functionToStub').throws(Error('error message'))

So in our case, the Express route test would look like so:

import request from 'supertest'
import sinon from 'sinon'
import app from '../../app'
import * as itemQueries from '../../db/queries/item.query'

describe('/api/search route', () => {
  it('should return a 500 when an error is encountered', async () => {
    // stub an error
    sinon.stub(itemQueries, 'search').throws(Error('db query failed'))

    await request(app) // pass Express app to supertest
      .post('/api/search') // call Express route we want to test
      .send({term: 'blah', num: 1}) // pass normally expected, valid data in request body
      .expect(500) // assert that we return a HTTP 500 response status code
  })
})

In the above test, we assert on the status code - .expect(500) - because if the database query fails and throws an error (maybe the database is down, for example), we expect to return a 500 Internal Server error code to the caller of the API.

Wrapping up

By stubbing a fake error in the test code, you're able to avoid hardcoding an error in your app code and mimic a real-world error scenario.

With that test case covered, this gives your app much more reliability.

And you don't just have to fake an error at the database layer, you can do it anywhere within your app. You can even mock calls to external services you don't own!

Knowing how to write tests in Node is one hurdle... understanding how to structure your project is another. Want an Express REST API structure template that makes it clear where your logic should go, and configures basic CI for you? Sign up below to receive that template, plus a post explaining how that structure works / why it's setup that way so you don't have to waste time wondering where your code should go. You'll also receive all my new posts directly to your inbox!

Subscribe for the repo!

No spam ever. Unsubscribe any time.