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)
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:
Do not declare done
as an argument.
The Resolution method overspecified
exception is thrown because you both:
return a Promise and also declare done
.
Promises and async/await don't use done
, at all.
Promises and async/await are equivalent.
Arrow functions (lambdas) in Mocha's callback should be avoided.
They lexically bind this
so shouldn't be used when declaring the callbacks of describe
,it
,before
and so on.
The test code itself that's tested within the same callbacks can contain whatever though. If you ever read this in a book, throw it away. This is bullshit of the highest degree. Functions and lambdas aren't perfectly interchangeable nor where they ever designed to be.
Right, so an actual answer is gonna be a bit long because it involves a bunch of discreet subjects, but generally speaking:
async
/await
and Promises
are equivalent. In fact, async
functions always implicitly return a Promise
.
Mocha (wrongly IMO) assumes you're aware of this & prominently shows examples of Promise
-based code instead of explicit async
/await
.
Mocha has 4 different types of tests; it attempts to resolve the type automatically but it requires that you setup the test in a specific way. Declaring done
tells Mocha this is a callback test, while returning a Promise
tells it it's a Promise-based
test.
You do both so it freaks out, basically asking you to choose one or the other.
Theres a specific way you can get both done
and Promise
s working as a Mocha test. As explained this is wrong yet it doesn't produce an error. However, that structure entirely misses the point of Promises
and it's almost certain you'll write your entire test suite in an incorrect and brittle manner if you keep going. Make sure you avoid this.
Now to get to the point quickly, here's what you do wrong, plain and simple:
(click on the sketch to zoom-in)
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.
This is a correct example:
For accessibility reasons, resummarised:
it()
-callbacks as an async
function.
regular functions
over arrow functions
in all Mocha callbacks. This is a common footgun but since it's not directly related to the OP I won't expand; if you need more details, read mochas docs.done
parameter from it()
callbacks.done
(in fact don't ever declare any parameters) when testing Promise
-based or async
/await
syntax.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 it
s 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.
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.
it
callback and use then
/catch
handlers to perform assertions.it
callback as async
and use await
to perform assertions.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.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. .
async
keywordJust 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.