expect(await fn()) vs await expect(fn()) for error tests with chai-as-promised

The problem scenario

Ah, writing tests for errors. It's fun, isn't it? Have you ever written a unit test expecting to catch an error with Chai, and gotten something like the below?

I don't understand why expect(await fn()).to.be.rejectedWith(I'm an error!) is failing... we're calling the function, getting an error, and asserting on that... why isn't it passing?

Writing tests can often fall to the wayside, and when you have a failing test that's driving you crazy, you're probably more likely to just strip out the test.

We want testing to be easy and somewhat enjoyable. If it's not, then something's wrong.

I've covered a bit about how to write tests for errors from Promises/async functions before, but now let's take a deeper look into why doing expect(await fn()) won't work when you're expecting an error/rejected promise.

What we're really doing when we write expect(await fn()).to.be.rejectedWith(error)

Suppose the function we're testing is the one below:

const someFn = async () => {
  throw new Error(`I'm an error!`)

And here's our test setup using Chai / chai-as-promised:

const chai = require('chai')
const chaiAsPromised = require('chai-as-promised')

const { someFn, otherFn } = require('./index')

const expect = chai.expect

We're purposefully throwing an error / rejecting the Promise for purposes on demonstrating testing for errors. In our test, when we do this:

expect(await fn()).to.be.rejectedWith(`I'm an error!`)

It is the same thing as doing this:

const res = await someFn()
expect(res).to.be.rejectedWith(`I'm an error!`)

Pulling the await someFn() result out into a variable helps make this clearer as to what's going on.

As we are not catching the result of the function, we don't catch the error. The error just ends up being printed to the console, and the test fails.

Side note: normally we should expect an UnhandledPromiseRejection to show up in the console as well, but Mocha has some in-built error handling / promise rejection handling that is catching this instead.

What we should be doing instead

Instead, what we should do to test for our error / rejected promise is this:

await expect(someFn()).to.be.rejectedWith(`I'm an error!`)

When we put the await in front of the expect, Chai / chai-as-promised is able to check for the rejected promise. We await on the assertion, and this allows us to catch and check the error.

Note: we could also use return instead of await as the framework will catch it.

Wrapping up

It's quirky things like this that can derail you in the JavaScript/Node world. And like I mentioned earlier, if tests are cumbersome to write, more often than not they just don't get written.

I have plenty more testing content planned for the future, so if you found this helpful and want to receive it directly to your inbox without having to remember to check back here, sign up below:

Subscribe for more Node and testing content!

No spam ever. Unsubscribe any time.