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 on top of Promises; which Mocha natively supports.

    Therefore, all you need is:

    async function getFoo() {
      return 'foo'
    }
    
    // fixes applied:
    // 1. no 'done' parameter
    // 2. replaced arrows/lambdas with plain functions.
    describe('#getFoo', function () {
      it('returns foo', async function () {
        const result = await getFoo()
        assert.equal(result, 'foo')
      })
    })
    

    ... there you go, async/await in Mocha.

    if you're skimming this just grab the above MCVE, note the following key points and you're good to go:

    Actual answer

    Right, so an actual answer is gonna be a bit long because it involves a bunch of discreet subjects, but generally speaking:

    Now to get to the point quickly, here's what you do wrong, plain and simple:

    (click on the sketch to zoom-in)

    shows a Mocha it() function. It's callback is declared as async. Drawn arrows point to a "done" argument of the it function callback, recommending to remove it. Some additional text recommends to avoid arrow functions in Mocha's describe, it and any other hook function and their callbacks. Finally points out that returning anything from the it callback is unecessary unless explicitly testing Promise code

    Very specifically, avoid this setup at all costs: It's the worst because it's incorrect but it might look like it works .. for a while.

    shows the worst example. A non-async it callback that declares done parameter. A Promise test calls the done parameter in  its callback. Mocha doesnt complaint about it but its an incorrect way of using Promises

    This is a correct example:

    shows the same it as above image but with all recommendations fixed.

    For accessibility reasons, resummarised:

    expanding on each:

    async/await and Promises are equivalent.

    async/await is a mechanism built on top of, and semantically equivalent to Promises, for lack of a better word.

    Anyone telling you otherwise is wrong. Theres no degrees of freedom here. This "detail" is the opening line of the TC39 proposal itself:

    The introduction of promises and generators in ECMAScript presents an opportunity to dramatically improve the language-level model for writing asynchronous code in ....

    Mocha's documentation assumes you know this and just gives out a Promise example that; if you only recently got involved with the language, you wouldn't know any better because async/await is ubiquitous now.

    Long-story short, whenever you see "supports Promises" you're safe to assume 100% that it also means, "works with async/await".

    So back to Mocha.

    Promise-based tests are explained in the docs. You just have to return the Promise to it()'s callback.

    Then:

    It's really as simple as that.

    Now, bearing in mind that async functions always implicitly return a Promise you could simply do the following:

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

    ... and therefore:

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

    No difference between async/await and Promise.

    Resolution method is overspecified

    This exception is thrown by Mocha when you setup your test in such a way that its not entirely clear what your intentions are.

    done is declared and (manually called) in an archaic model of asynchronous programming that uses callback fuctions.
    It's rare that you see code like this anymore unless youre working with legacy systems.

    So for all intents and purproses, unless you know what you're doing, DO NOT declare done as a function argument.

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

    That includes (especially this) passing it as an argument to its callback because that trips up the internal mechanism that hints to Mocha that your intent isn't to actually run a Promise-based test but rather a done/callback-based test.

    I explain this clearly in the sketch above but here it is again:

    async function getFoo() {
      return 'foo'
    }
    
    describe('#getFoo', function () {
      it('resolves with foo', function (done) { // <-- !!
        return getFoo().then(result => {
          assert.equal(result, 'foo')
        })
      })
    })
    

    This throws that exception because you're already returning a Promise so it's classified as a Promise test which doesn't need done since it has everything necessary to know when the asynchronous action has ended.

    Mocha test types

    The important context here is that Mocha supports 4 different types of tests.

    It's crucial that you 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.

    The mere existence of an argument in it, (in this case done) is hinting to Mocha that the test is callback-based and not Promise/async-based. It's just how the internals in Mocha attempt to figure out what your intent is.

    Again 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. .

    asynchronous programming vs async keyword

    Just to settle any confusion about terminology:

    Asynchronous is a Greek word meaning not in synchrony = not following in some apparent manner = not one-code-line-after-the-other*. This is a general programming model thats central to JavaScript since its inception. Shortened it's also called "async" code.

    Confusingly enough, async also exists as a language keyword that marks a function as asynchronous in nature.

    The point is that asynchronous operations and the async keyword revolve around the same concept but they are not the same thing.

    You can handle asynchronous operations with other constructs apart from async functions, e.g: Promises, callback functions, events etc. If I'm highlighting async I'm talking about the keyword otherwise I'm talking about the general concept.