.   .   .
We're always looking for great talent‼️ 🚀😄 And we're passionate about driving innovation and improving lives. Join us as we help social impact organizations and enterprise customers build their web apps. Apply today!
.   .   .

Recently I was adding tests to an app written in node and express. The app utilizes mocha for testing.

The previously written tests, which used Promises, were working well. We had recently upgraded node for the app so I wanted to write my new tests with Async / Await.

I don’t get into the details of how our app is actually written, just the issue I ran into and the solution I found.

The Promises based tests looked something like:

describe('Create', () => {
  it('Should test things.', done => {
    let exampleThing = factory.buildSync('thing');
    request(app)
      .post('/things')
      .send({ thing: exampleThing })
      .then(res => {
        expect(res.status).to.equal(200);
        expect(res.body.example.text).to.equal('example');
        done();
      })
      .catch(err => {
        logMyErrors(err);
      });
  });
});

My first async tests were written like this:

describe('Delete', () => {
  it('Another sample test.', async done => {
    try {
      let user = await factory.create('dude');
      let res = await request(app).delete(`/dude/${user.id}/`);

      expect(res.status).to.equal(200);
      done();
    } catch (error) {
      logMyErrors(error);
    }
  });
});

All my tests were passing… great! Well, not really. My try / catch was actually disguising that when a test should have failed it passed. When I purposefully tried to break this, it still gave the green light on all my tests.

In my debugging I removed the try / catch and would get an UnhandledPromiseRejectionWarning and did some more research.

I found some advice that inside my catch block I needed to include a callback as well. Normally the callback is called done(), so my new function looked like:

describe('Delete', () => {
    it('Sample Test.', async done => {
      try {
        let user = await factory.create('dude');
        let res = await request(app).delete(`/dude/${user.id}/`);

        expect(res.status).to.equal(200);
        done();
      } catch (error) {
        done(error);
      }
    });
});

I then received the error Resolution method is overspecified. Specify a callback *or* return a Promise; not both. As seen here, with mocha version v3.0.0 and newer, we can either use a done callback or return promise, but can’t do both.

From further research I found it is not good to call done() inside the try { } block. I found this advice in multiple places but one reason I found was if done() throws an error, then you will catch it and call done(error). In that case done() is called two times.

The best option I found was actually from a comment on this article by Jason Jarrett, which creates a higher order function that looks like this:

var mochaAsync = (fn) => {
  return done => {
    fn.call().then(done, err => {
      done(err);
    });
  };
};

We then pass in our async function so that my mocha test now looks like this:

describe('Delete', () => {
    it('Sample Test.', mochaAsync(async () => {
       let user = await factory.create('dude');
       let res = await request(app).delete(`/dude/${user.id}/`);
       expect(res.status).to.equal(200);
    });
});

Here we pass our async function into the higher order function which handles the rejections.

I hope this short post was helpful. If you have a question or see an error, let me know!