Search code examples
javascriptmocha.js

How to test async code with mocha using await


How do I test async code with mocha? I wanna use multiple await inside mocha

var assert = require('assert');

async function callAsync1() {
  // async stuff
}

async function callAsync2() {
  return true;
}

describe('test', function () {
  it('should resolve', async (done) => {
      await callAsync1();
      let res = await callAsync2();
      assert.equal(res, true);
      done();
      });
});

This produces error below:

  1) test
       should resolve:
     Error: Resolution method is overspecified. Specify a callback *or* return a Promise; not both.
      at Context.it (test.js:8:4)

If I remove done() I get:

  1) test
       should resolve:
     Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/tmp/test/test.js)

Solution

  • async/await is nothing more than syntactic sugar built on top of Promises which Mocha natively supports.

    Here's how it's done -s with full async/await syntax:

    async function getFoo() {
      return 'foo'
    }
    
    describe('#getFoo', () => {
      it('returns foo', async () => {
        const result = await getFoo()
        assert.equal(result, 'foo')
      })
    })
    

    As said, you can also use Promises.

    You just have to return the Promise to it()'s callback.

    • If the Promise resolves then the test passes.
    • if the Promise rejects then the test fails.

    As simple as that.

    Since async functions always implicitly return a Promise you can just do:

    // technically returns a Promise which resolves with 'foo'
    async function getFoo() {
      return 'foo'
    }
    
    describe('#getFoo', () => {
      it('resolves with foo', () => {
        return getFoo().then(result => {
          assert.equal(result, 'foo')
        })
      })
    })
    

    which effectively the exact same as this:

    In either case, do not declare done as a function argument. This is what's catching most people off-guard.

    If you use any of the methods described above - which involve Promise and async/await - you need to remove done completely from your testing setup.

    That includes passing it as an argument to its callback because that hints to Mocha that this isn't a Promise-based test but rather a done/callback-based test.

    Mocha has 4 different types of tests. It's important to pick one style and roll with it all the way because Mocha makes assumptions about the type of test based on how you set it up.

    • Purely Promise-based: return the Promise to the it callback and use then/catch handlers to perform assertions.
    • Promise-based and async/await: Mark the it callback as async and use await to perform assertions.
    • Callback-based via done: do asynchronous operations and assertions and when you're done, explicitly call done(). This is a relic from the old days when we had to deal with callbacks, before Promise or async/await came about. It still has some use cases, mainly testing callback-style code or events.
    • Purely synchronous: You don't need to return anything nor declare any special arguments.

    If you return both a Promises and also declare done as an argument, Mocha will start complaining:

    Error: Resolution method is overspecified. Specify a callback or return a Promise; not both

    Because it's conflicted as to which type of test you want to use.

    The done method is only used for testing callback-based or event-based code. You shouldn't use it if you're testing Promise-based or async/await functions. If you find yourself having to use both done and Promise.then/Promise.catch semantics, you've probably misunderstood how Promises work and failing tests would be the least of your worries.