Testing async/await with Babel and Mocha

I was curious about testing ES6/7 code transpiled with Babel. Getting it to work is fairly straightforward. Here's what I found...

Setup

The documentation for Babel tells you to setup an NPM script to run mocha tests as such:

{
  "scripts": {
    "test": "mocha --compilers js:babel/register"
  }
}

This is all well and good if you want to run the default options, but since async/await is part of the ES7 features, we need to do things a bit differently.

In order to get async/await working, you will need to setup a bootstrap file for the Babel dependency. Create a file in your project root called mocha-babel.js. Add the Babel initialization code to it as so:

require('babel/register')({  
  'optional': [ 'es7.asyncFunctions' ]
});

This will register the require hook which is used to automatically transpile code on the fly. Adding the es7.asyncFunctions flag will allow us to use async/await.

You can now use this file with the --require flag when calling Mocha. The --require flag will import the specified module before running Mocha. This means that Babel will be setup to automatically transpile code prior to the Mocha test running! This is exactly what we need.

So now you can set your NPM script as such:

{
  "scripts": {
    "test": "mocha --require mocha-babel"
  }
}

Note, you can also set this up using grunt-mocha-test by specifying the require option to point to our bootstrap file.

Tests

The next part is actually writing tests.

First lets see what a successful test looks like. The function we want to test uses async/await syntax... though all it's really doing is returning string concatination. No matter, under the covers, it's still returning a Promise, which is the basis of how we write the test.

exports.hello = async function fail(name) {  
  return 'Hello ' + name;
}
describe('A success', function() {  
  it('should return hello + input', function(done) {
    sut
      .hello('world')
      .then(function(result) {
        expect(result).to.equal('Hello world');
        done();
      })
      .catch(done);
  });
});

The test calls our function hello with input. Since the function is returning a Promise, we can convert from async/await syntax to Promise synxtax. We use then to handle the success condition and execute the assertion. Finally, we call done to trigger the end of the async test.

But what happens if we have a failed assertion?

describe('An assertion failure', function() {  
  it('should be caught', function(done) {
    sut
      .hello('world')
      .then(function(result) {
        expect(result).to.equal('WRONG');        
      })
      .catch(done);
  });
});

The above code attempts to assert that 'Hello world' equals 'Wrong'. Fortunately, the catch will handle this and spit out our exception information by passing it to done

  1) An assertion failure should be caught:

      AssertionError: expected 'Hello world' to equal 'WRONG'
      + expected - actual

      -Hello world
      +WRONG

      at d:/node/babel-mocha-test/test/funcs-tests.js:21:27

Well that's nice, but what about when our code throws an exception?

Again the catch will handle function exceptions appropriately. This is all due to async wrapping the underlying code for us.

exports.boom = async function fail() {  
  throw new Error('boom');
}
describe('An internal failure', function() {  
  it('should be caught', function(done) {
    sut
      .boom()      
      .catch(done);
  });
});
  2) An internal failure should be caught:
     Error: boom
      at Object.fail$ (d:/node/babel-mocha-test/src/funcs.js:7:9)
      at Object.fail (d:/node/babel-mocha-test/src/funcs.js:6:35)
      at Context.<anonymous> (d:/node/babel-mocha-test/test/funcs-
tests.js:30:8)  

Lastly, what happens if we fat-finger our test and it has an issue? Fortunately, Mocha itself will handle that.

describe('A non-existant function', function() {  
  it('should be caught', function(done) {
    sut
      .doesNotExist()
      .catch(done);
  });
});
  3) A non-existant function should be caught:
     TypeError: undefined is not a function
      at Context.<anonymous> (d:/node/babel-mocha-test/test/funcs-
tests.js:41:8)

Hopefully that puts your mind at ease about testing async/await. Just remember to end the Promise chain with catch(done) and you should be good!

For a full working example check out:
https://github.com/bmancini55/babel-mocha-test

comments powered by Disqus