Faking errors to test error scenarios in Express API's

Corey Cleary - Feb 13 '20 - - Dev Community

Originally published at coreycleary.me. This is a cross-post from my content blog. I publish new content every week or two, and you can sign up to my newsletter if you'd like to receive my articles directly to your inbox! I also regularly send cheatsheets and other freebies.

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!

Love JavaScript but still getting tripped up by unit/integration tests? I publish articles on JavaScript and Node every 1-2 weeks, so if you want to receive all new articles directly to your inbox, here's that link again to subscribe to my newsletter!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .