Why isn't this unit test catching an error from this async/await function?
Tags: javascript, node, testing, unittesting
Published: 2018-09-07 12:49:18

---
title: Why isn't this unit test catching an error from this async/await function?
published: true
description: 
tags: javascript,nodejs,testing,unittesting
---
<em><strong>Originally published at <a href="https://www.coreycleary.me/why-isnt-this-unit-test-catching-an-error-from-this-async-await-function/">coreycleary.me</a></strong>. This is a cross-post from my content blog. I publish new content every week or two, and you can <a href="https://www.coreycleary.me/about/">sign up to my newsletter</a> if you'd like to receive my articles directly to your inbox! I also regularly send cheatsheets, links to other great tutorials (by other people), and other freebies.</em>

When you're writing unit tests for asynchronous functions in JavaScript, one test case you'll usually want to have is making sure the async function throws an error in case of an error scenario.

Let's imagine writing a test for an item function that calls a database and returns an item:
```javascript
const fetchItem = async function (itemName) {
  if (typeof itemName !== 'string') {
    throw new Error('argument should be a string')
  } else {
    return await db.select(itemName)
  }
}

module.exports = {
  fetchItem
}
```
<em>Note: normally I don't like doing type checks on arguments, but this is easy for demonstration purposes.</em>

A reasonable unit test for this might look like: 
```javascript
const { fetchItem } = require('../path/to/fn')

describe('#fetchItem', () => {
  it('should catch an error', async () => {
    await expect(fetchItem(3)).to.eventually.throw()
  })
})
```

In this case, we call the `fetchItem()` function with an argument that is not a string (which our database query will expect). It's an async function so we `await` it and expect it to eventually `throw`, since the function will throw a `new Error` if passed a non-string argument. 

It seems like it should pass, right?

Then why does the test fail with an uncaught error? Why does the error just show up in the console without the test passing?

Let's take a look at why it's not working, and how to fix it...

<h3>Why doesn't it work like you expect it to?</h3>

The beauty of `async/await` is that it makes <em>asynchronous</em> code read as if it were <em>synchronous</em> code. So synchronous that it can be easy to forget you're still dealing with async code.

It's important to remember that in JavaScript whenever you have a function with the `async` keyword, it always returns a Promise. And when you have a function that returns a Promise it's either resolved or rejected.

When we throw that error like we did in the `fetchItem()` function,
```javascript
if (typeof itemName !== 'string') {
    throw new Error('argument should be a string')
}
```
it's really <strong>rejecting</strong> the Promise. It will reject with an error, but it's a rejected Promise, nonetheless.

<h3>The fix</h3>

The fix for this is very simple. Import <a href="https://github.com/domenic/chai-as-promised" rel="noopener" target="_blank">chai-as-promised</a> into your tests like so:
```javascript 
const chai = require('chai')
const chaiAsPromised = require('chai-as-promised');

const expect = chai.expect
chai.use(chaiAsPromised)
```
Then change the test to be:
```javascript 
describe('#fetchItem', () => {
  it('should catch an error', async () => {
    await expect(fetchItem(3)).to.be.rejected
  })
})
```
All that changed was instead of `to.eventually.throw()`, it becomes `to.be.rejected`. If you want to test to make sure it's rejected with the right error message, you can change it to `to.be.rejectedWith('argument should be a string')`.

<h3>A note on return vs await</h3>
Chai will wait for Promises, so instead of using `await`
```javascript
await expect(fetchItem(3)).to.be.rejected
```
you could use `return`
```javascript
return expect(fetchItem(3)).to.be.rejected
```
I prefer to use `await` as it reminds me that I'm working with an `async` function, but this is worth pointing out in case you find other examples using `return`.

<h3>Wrapping up</h3>
With native Promises, where you explicitly reject the Promise when you hit an error scenario, it's a bit easier to remember that you're testing for a rejected Promise, not a caught error. 

I've written plenty of <strong>working</strong> tests for `async/await` functions that throw errors, but it's still an easy thing to forget. I encountered it recently when I was writing the code for the post on <a href="https://www.coreycleary.me/know-what-to-test-using-these-recipes-node-service-that-calls-a-database/" rel="noopener" target="_blank">scenarios for unit testing Node services</a>, which involved a lot of asynchronous code. And by the way, if you're looking for a list of common tests you should have for Node services, definitely check out that post.

I think testing should be as easy as possible in order to remove the barriers to actually writing them. It's one thing to get stuck on code - you don't have any choice but to fix it. But its another thing to get stuck on tests - with tests you <em>technically can</em> skip them.

I'm trying to make testing and other things in JavaScript easier by sending out tutorials, cheatsheets, and links to other developers' great content. Here's that link again to <a href="https://www.coreycleary.me/about/">sign up to my newsletter</a> again if you found this tutorial helpful!