Search code examples
typescriptvisual-studio-codemocha.js

Mocha tests don't run when called from an async function


I'm using Microsoft's vscode-test module to test a VS code extension. I started with their example test runner script from here - each runner script exports a function run which takes no args and returns a Promise

export function run(): Promise<void> {
    // Create the mocha test
    const mocha = new Mocha({
        ui: 'tdd'
    });
 
    const testsRoot = path.resolve(__dirname, '..');

    return new Promise((c, e) => {
        glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
            if (err) {
                return e(err);
            }

            // Add files to the test suite
            files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));

            try {
                // Run the mocha test
                mocha.run(failures => {
                    if (failures > 0) {
                        e(new Error(`${failures} tests failed.`));
                    } else {
                        c();
                    }
                });
            } catch (err) {
                console.error(err);
                e(err);
            }
        });
    });
}

I needed to change this a bit because the latest version of glob (9.x) returns a Promise rather than taking a callback, so I rewrote it like this:

export async function run (): Promise<void> {
  // Create the mocha test
  const mocha = new Mocha({
    ui: 'tdd',
    color: true
  })
  const testsRoot = path.resolve(__dirname, '..')
  return await new Promise((resolve, reject) => {
    glob('**/**.test.js', { cwd: testsRoot })
      .then(files => {
        // Add files to the test suite
        files.map(f => path.resolve(testsRoot, f))
          .forEach(f => mocha.addFile(f))
        // Run the mocha test
        mocha.run((failures: number) => {
          if (failures === 0) {
            resolve()
          } else {
            reject(new Error(`Test failures ${failures}`))
          }
        })
      }).catch(err => {
        console.error(err)
        reject(err)
      })
  })
}

this works but I was thinking it would be nicer to be able to use async/await a bit more.

export async function run (): Promise<void> {
  // Create the mocha test
  const mocha = new Mocha({
    ui: 'tdd',
    color: true
  })
  const testsRoot = path.resolve(__dirname, '..')
  const files = await glob('**/**.test.js', { cwd: testsRoot })
  files.map(f => path.resolve(testsRoot, f))
    .forEach(f => mocha.addFile(f))
  // Run the mocha test
  mocha.run((failures: number) => {
    if (failures > 0) {
      throw new Error(`Test failures ${failures}`)
    }
  })
}

Unfortunately, this doesn't run any tests. It definitely reaches the Mocha suite definitions but the actual tests are never called. I suspect somewhere a promise is not being resolved before the program as a whole finishes? My understanding was that making a function async causes it to return a promise and throwing is equivalent to calling the reject part of it.

Can anyone help point out where I'm going wrong?


Solution

  • You are implicitly skipping the asynchronously executing Mocha suite. There's neither any await associated with your call to mocha.run, nor any promisifying of the callback.

    At the crucial point you run the mocha suite you want something like...

    const failures = await new Promise<number>((resolve) => mocha.run(resolve));