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

Corey Cleary - Jan 4 '19 - - 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!

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
chai.use(chaiAsPromised)

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.

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 to the mailing list here!

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