mocha: Describe block with async function behaving weirdly

Mocha 3.5 Node 8.4

describe('Test Suite 1', async function(){
  let email = await faker.internet.email();
  it('should print email', function(){
     email.should.not.be.a('string')
   )}
})

Running this test should give

Test Suite 1
   ✓should print email

But its giving 0 passing

Also modifying the above code to

describe('Test Suite 1', async function(){
  let email;
  it('should print email', async function(){
     email = await faker.internet.email()
     email.should.not.be.a('string')
   )}
})

Runs the test but doesn’t print the name of describe i.e Test Suite 1 ✓should print email Removing the async from describe and putting it in it works fine

 describe('Test Suite 1', function(){
  it('should print email',async  function(){
     let email = await faker.internet.email();
     email.should.not.be.a('string')
   )}
})
Test Suite 1
   ✓should print email

Also .only and .skip do not work when async function is included in the describe block

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 28
  • Comments: 40 (10 by maintainers)

Commits related to this issue

Most upvoted comments

@ondratra, why can’t before() work for you?

“test.ts”

import testComponent1 from './subtests/component1';

describe('1st level describe', () => {
  let prerequisites;

  before(async () => {
    prerequisites = await serverINeedToRunBeforeTests();
  });

  describe('2nd level describe', () => {
    testComponent1(prerequisites);
  });

  after(() => {
    disconnectFromServerINeedToRunBeforeTests();
    prerequisites = null;
  });
});

Is there any plan to fix this in the long term?

That mocha reports “all good” after “0 Passed” (because it thought no tests had been defined) is one of the problems here. It’s easy to blindly switch to imports and see “all green” on tests, not realizing that tests were not even being run.

@Mikkal24 Have you see the (new in v9.1) --fail-zero option?

I am aware that that switching to imports results in the tests not executing. I assume this is because the import function returns a promise and describe does not support promises which was the purpose of this issue.

I just wanted to tack on to what @WORMSS said in 2018 because while import was a “nice to have” it’s now required in the official standard of Node.js since require doesn’t exist in ESM.

I can only hope Mocha devs will support this syntax in the future.

describe("some group", async ()=>{
    await import("myModule.js");
    await import("myOtherModule.js");
})

this would help a lot with maintaining large code bases. (TBF I’ve only got like 100 test files, I would be desperate for this if I had 4200 lol)

Async functions are syntactic sugar for returned promise chains, and Mocha’s describe blocks do not support (as in waiting for resolution of) returned promises. Failing to warn about it is perhaps less helpful than it could be, but the behavior is pretty much expected under the current design.

For cases where the names of the tests or how many tests there are can only be determined asynchronously, there is the --delay option, although that still requires the entire describe to occur after the asynchronous info is obtained (it still must be called synchronously inside describe before the describe returns).

If you don’t need the data to define the tests but only to run them, you can use async before/beforeEach hooks to get it (that would be the obvious way to do these trivial examples), or even async tests with the initial methods to obtain the data written into the start of the test.

If you can come up with a real use case where --delay’s limitations are needlessly awkward but it can’t just be done with before/beforeEach since the definition of tests depends on it, then a case could be made for adding promise support (from which async function support would automatically be derived) to describe.

I struggled with this same problem for a while (certain tests mysteriously not running) until I found this issue posted. I had just assumed describe would wait for promises the same way it does. I can work around it, but it would be wonderful if describe could just recognize async functions and behave accordingly.

@boneskull can we reopen this as it’s still not fixed?

In TypeScript projects, the lack of ability to use async describe functions leads to the following:

I want to write:

describe('express middleware', async () => {
  const {logger, mw} = await elb.middleware({level: 'info'});
  // ...
}

What I need to write:

describe('express middleware', () => {
  type ThenArg<T> = T extends Promise<infer U> ? U : T;
  type ElbThenType = ThenArg<ReturnType<typeof elb.middleware>>;
  let logger: ElbThenType['logger'];
  let mw: ElbThenType['mw'];

  before(async () => {
    ({logger, mw} = await elb.middleware({level: 'info'}));
  });

It would be quite handy if describe were to wait for the promise to resolve if one was returned. Is that possibility, or is there something fundamental that prevents this from ever happening.

I’d use this approach

const validatePropChanges = curry((original, path, obj) {/* yadda yadda */});

suite('muh data', function() {
   let validate;
   let DATA;
   before(async function() {
     const DATA = await fetch('data.json').then(jsonify);
     validate = validatePropChanges(DATA);
   })


  test('incrementFoo only increments foo', () => {
    const cloned = clone(DATA);
    incrementFoo(cloned);
    cloned.foo.should.equal(DATA.foo + 1);
    validate('foo', DATA)
  });

I want to fetch test data and use it to partially apply a validation function. doing it in beforeEach would defeat the purpose, since I wouldn’t have the data outside the scope of the test

suite('muh data', async () => {
  const DATA = await fetch('data.json').then(jsonify);

  // checks that only path has changed
  // original -> path -> obj -> assertion
  const validatePropChanges = curry((original, path, obj) {/* yadda yadda */});

  const validate = validatePropChanges(DATA);

  test('incrementFoo only increments foo', () => {
    const cloned = clone(DATA);
    incrementFoo(cloned);
    cloned.foo.should.equal(DATA.foo + 1);
    validate('foo', DATA)
  });

Just to add to the discussion that our company got caught out by the non-async nature of describe when a developer changed from require to import to match the rest of the system. Sadly this caused no errors, and was only during the addition of .only() by myself that the test reported 0 Passed when I knew there should be 38 that either passed or failed.`

describe.only('aqs.utils', async () => {
  await import('./equals');
  await import('./gas');
  await import('./notEquals');
  await import('./startsWith');
  await import('./wordSearch');
});

We went back to the original and now works as expected, but I think we would prefer to use import

describe.only('aqs.utils', () => {
  require('./equals');
  require('./gas');
  require('./notEquals');
  require('./startsWith');
  require('./wordSearch');
});

wanted to add to this by mentioning that ESM is now the official standard for Node.js and require is no longer available within ESM scope.

I do love splitting up my tests this way and I hope Mocha can support asynchronously importing test files in the future. IMHO this issue should be opened back up.

[edit]: it also looks like people were looking at Jest to support the same thing for similar reasons => https://github.com/facebook/jest/issues/2235

Just to add to the discussion that our company got caught out by the non-async nature of describe when a developer changed from require to import to match the rest of the system. Sadly this caused no errors, and was only during the addition of .only() by myself that the test reported 0 Passed when I knew there should be 38 that either passed or failed.`

describe.only('aqs.utils', async () => {
  await import('./equals');
  await import('./gas');
  await import('./notEquals');
  await import('./startsWith');
  await import('./wordSearch');
});

We went back to the original and now works as expected, but I think we would prefer to use import

describe.only('aqs.utils', () => {
  require('./equals');
  require('./gas');
  require('./notEquals');
  require('./startsWith');
  require('./wordSearch');
});

I believe they want to keep the “build up the structure of where the tests are” as synchronous as possible.

@juergba it looks like more and more people are falling into this trap. It’s a bit dangerous when the testing tool just stops running part of the tests without any errors or warnings. And the lack of support for asynchronous describe violates the golden rule of predictable behavior. If async it is supported, it’s reasonable to assume that async describe should also works.

For me, the main reason for using async describe is to import ESM modules. And even if there are several stages of test execution, why not wait for async describe?

That --fail-zero would only help if ALL your tests were in that one describe… Only about 400 test were in the aqs.utils location. The other 4200 tests were scattered throughout many other files. As I said back in 2018, it was ONLY because I used .only( that I even noticed this problem at all.

I have found another way to get things done. May be this could work for someone.

describe('Parent', () => {
    let array: any = [];
    before(async () => {
        array = await someAsyncDataFetchFunction();
        asyncTests();
    });
    it('Dummy test to run before()',async () => {
        expect(0).to.equal(0); // You can use this test to getting confirm whether data fetch is completed or not.
    });
    function asyncTests() {
        array.forEach((currentValue: any) => {
            describe('Child', async () => {
                it('Test '+ currentValue ,() => {
                    expect(currentValue).to.equal(true);
                })
            })
        });
    }
});

That’s how I achieved the assertion on every element of the array. (Array data is being fetch asynchronously). Kindly correct me if I’m doing it wrong.

LOL…

  it('should do magic', async () => {
    // $FlowMocha
    const res = await chai.request(expressApp).put('/some/url').send({ ... });
    res.body.should.be.equal(true);
  });

“.flowconfig”

suppress_comment= \\(.\\|\n\\)*\\$FlowMocha

Maybe…

let dbconn;

before(async () => {
  dbconn = await spinOffMongoAndReturnConnectionString();
});

describe("Suite", () => {
  let expressApp;

  before(() => {
    const configuration = {
      ...readConfigFromEnvSync(),
      dbconn
    };
    expressApp = createTestAppSync(configuration);
  });

  it('should do magic', async () => {
    const res = await chai.request(expressApp).put('/some/url').send({ ... });
    res.body.should.be.equal(true);
  });

  after(() => {
    expressApp = null;
  });
})

after(() => {
  dbconn = null;
});