How can I build my test suite asynchronously?

You should run Mocha with the --delay option, and then use run() once you are done building your test suite. Here is an example derived from the code you show in the question:

'use strict';

function test() {
    console.log(1);
    describe('Unit Testing', () => {
        console.log(2);
        it("test", () => {
            console.log(3);
        });
    });

    // You must use --delay for `run()` to be available to you.
    run();
}

setTimeout(test, 1000);

I’m using setTimeout to simulate an asynchronous operation. Using --delay and run() allows you to build a suite that is the result of an asynchronous computation. Note, however, that the suite must be built in one shot. (You cannot have an asynchronous process inside describe that will make calls to it. This won’t work.)


One thing you should definitely not do is what rob3c suggests: calling describe or it (or both) from inside a hook. This is a mistake that every now and then people make so it is worth addressing in details. The problem is that it is just not supported by Mocha, and therefore there are no established semantics associated with calling describe or it from inside a hook. Oh, it is possible to write simple examples that work as one might expect but:

  1. When the suite becomes more complex, the suite’s behavior no longer corresponds to anything sensible.

  2. Since there are no semantics associated with this approach, newer Mocha releases may handle the erroneous usage differently and break your suite.

Consider this simple example:

const assert = require("assert");

const p = Promise.resolve(["foo", "bar", "baz"]);

describe("top", () => {
    let flag;
    before(() => {
        flag = true;
        return p.then((names) => {
            describe("embedded", () => {
                for (const name of names) {
                    it(name, () => {
                        assert(flag);
                    });
                }
            });
        });
    });

    after(() => {
        flag = false;
    });

    it("regular test", () => {
        assert(flag);
    });
});

When we run it, we get:

  top
    ✓ regular test

  embedded
    1) foo
    2) bar
    3) baz

  1 passing (32ms)
  3 failing

  // [stack traces omitted for brevity]

What’s going on here? Shouldn’t all the tests pass? We set flag to true in the before hook for the top describe. All tests we create in it should see flag as true, no? The clue is in the output above: when we create tests inside a hook, Mocha will put the tests somewhere but it may not be in a location that reflects the structure of the describe blocks in the code. What happens in this case is that Mocha just appends the tests created in the hook the the very end of the suite, outside the top describe, so the after hook runs before the dynamically created tests, and we get a counter-intuitive result.

Using --delay and run(), we can write a suite that behaves in a way concordant with intuition:

const assert = require("assert");

const p = Promise.resolve(["foo", "bar", "baz"]).then((names) => {
    describe("top", () => {
        let flag;
        before(() => {
            flag = true;
        });

        after(() => {
            flag = false;
        });

        describe("embedded", () => {
            for (const name of names) {
                it(name, () => {
                    assert(flag);
                });
            }
        });

        it("regular test", () => {
            assert(flag);
        });
    });
    run();
});

Output:

  top
    ✓ regular test
    embedded
      ✓ foo
      ✓ bar
      ✓ baz


  4 passing (19ms)

Leave a Comment