devnull.land

Miscellaneous bits and bobs from the tech world

Asynchronously generating tests using mocha

10/14/2021, 2:50:06 PM


The great thing about Mocha is that it supports promises, so it's a cinch to integrate it into your existing workflow.

It came in handy in NodeBB when we migrated much of our code to async/await from callbacks.

However, I ran into a limitation when attempting to write a test file that dynamically generated its own tests. My specific use cases were:

  1. End-to-end testing of the NodeBB API. I needed to retrieve all mounted routes and test that its responses matched the documented OpenAPI schema.
  2. Testing of our internationalization files. Our translation efforts are powered by Transifex, but there were cases where the pulled files did not match the source language files, or we had forgotten to push source files to Transifex for translating.

In both cases, I wanted to dynamically generate tests given some external state (e.g. tests for each of our ~45 supported languages), and while Mocha does allow you to dynamically generate tests, it quickly falls over when you attempt to make an asynchronous call before generating the tests. Top-level await would resolve the issue, but what reprieve do those of us not using ESM have?

For example, code like this would behave in unexpected ways, with folders remaining undefined.

describe('i18n', () => {
  let folders;
  before(async () => {
    folders = await getLanguageFolders();
  });
  
  folders.forEach((folder) => {
    it('should pass some test', () => {
      assert(...
    });
  });
});

Even though before is executed, mocha only adds the passed-in function to its queue, but doesn't execute it immediately until the entire file is parsed, so the folders.forEach is executed against an undefined variable.

The workaround

I ended up wrapping the folders.forEach in an it block, so that it ran in the written order.

describe('i18n', () => {
  let folders;
  before(async () => {
    folders = await getLanguageFolders();
  });
  
  it('', () => {
    folders.forEach((folder) => {
      describe(folder, () => {
        it('should pass some test', () => {
          assert(...
        });
      });
    });
  });
});

There may be a better way, although I have not discovered it yet.