Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Verify that an exception is thrown using Mocha / Chai and async/await

I'm struggling to work out the best way to verify that a promise is rejected in a Mocha test while using async/await.

Here's an example that works, but I dislike that should.be.rejectedWith returns a promise that needs to be returned from the test function to be evaluated properly. Using async/await removes this requirement for testing values (as I do for the result of wins() below), and I feel that it is likely that I will forget the return statement at some point, in which case the test will always pass.

// Always succeeds function wins() {   return new Promise(function(resolve, reject) {     resolve('Winner');   }); }  // Always fails with an error function fails() {   return new Promise(function(resolve, reject) {     reject('Contrived Error');   }); }  it('throws an error', async () => {   let r = await wins();   r.should.equal('Winner');    return fails().should.be.rejectedWith('Contrived Error'); }); 

It feels like it should be possible to use the fact that async/await translates rejections to exceptions and combine that with Chai's should.throw, but I haven't been able to determine the correct syntax.

Ideally this would work, but does not seem to:

it('throws an error', async () => {   let r = await wins();   r.should.equal('Winner');    (await fails()).should.throw(Error); }); 
like image 980
plexer Avatar asked Aug 02 '17 16:08

plexer


People also ask

How do you test async Mocha?

To indicate that a test is asynchronous in Mocha, you simply pass a callback as the first argument to the it() method: it('should be asynchronous', function(done) { setTimeout(function() { done(); }, 500); });

Does async await block execution?

await only blocks the code execution within the async function. It only makes sure that the next line is executed when the promise resolves. So, if an asynchronous activity has already started, await will not have an effect on it.


1 Answers

The problem with this approach is that (await fails()).should.throw(Error) doesn't make sense.

await resolves a Promise. If the Promise rejects, it throws the rejected value.

So (await fails()).should.throw(Error) can never work: if fails() rejects, an error is thrown, and .should.throw(Error) is never executed.

The most idiomatic option you have is to use Chai's rejectedWith property, as you have shown in your question.

Here's a quick example. Not much is different from what you've demonstrated in your question; I'm just using async functions for wins() and fails() and expect instead of should. Of course, you can use functions that return a Promise and chai.should just fine.

const chai = require('chai') const expect = chai.expect chai.use(require('chai-as-promised'))  // Always succeeds async function wins() {   return 'Winner' }  // Always fails with an error async function fails() {   throw new Error('Contrived Error') }  it('wins() returns Winner', async () => {   expect(await wins()).to.equal('Winner') })  it('fails() throws Error', async () => {   await expect(fails()).to.be.rejectedWith(Error) }) 

If you like want your wins() test to resemble your fails() test more closely, you can write your wins() test like so:

it('wins() returns Winner', async () => {   await expect(wins()).to.eventually.equal('Winner') }) 

The key thing to remember in either of these examples is that chai-as-promised returns promises for its functions such as rejectedWith and eventually.something. Therefore you must await them in the context of an async test function, or else failing conditions will still pass:

async function wins() {   return 'Loser' }  async function fails() {   return 'Winner' }  it('wins() returns Winner', async () => {   expect(wins()).to.eventually.equal('Winner') })  it('fails() throws Error', async () => {   expect(fails()).to.be.rejectedWith(Error) }) 

If you ran the tests with the code above, you'd get the following:

$ npm test  > [email protected] test /home/adaline/code/mocha-chai-async > mocha .      √ wins() returns Winner (node:13836) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rej ection id: 1): AssertionError: expected 'Loser' to equal 'Winner' (node:13836) [DEP0018] DeprecationWarning: Unhandled promise rejections are dep recated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.   √ fails() throws Error (node:13836) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rej ection id: 2): AssertionError: expected promise to be rejected with 'Error' but  it was fulfilled with 'Winner'    2 passing (11ms) 

As you can see, the chai assertions actually failed, but they were failed in the context of a Promise that no one ever awaited or catched. So Mocha sees no failure and marks the tests as though they passed, but Node.js (in behaviour that will change in the future as noted above) prints the unhandled rejections to the terminal.

like image 57
Adaline Simonian Avatar answered Sep 21 '22 18:09

Adaline Simonian