Migrating from Mocha to AVA

AVA logo

Ever since I ditched jQuery soup and started writing proper JavaScript applications, I've written unit tests for my frontend code. And for as long as I've written JavaScript unit tests, I've used Mocha. Mocha has been the de facto test runner in the JavaScript community for a while now. If it gets the job done and everyone uses it, then I should use it too. Right?

Technical debt cartoon

This has been my philosophy for the past few years, and Mocha has been more or less fine. Recently, however, I've heard a lot of buzz about a new test runner called AVA. Wanting to see what all the fuss was about, I migrated one of my project's test suites from Mocha to AVA. Since then I haven't been able to go back.


What's wrong with Mocha anyway?

It wasn't until after migrating to AVA that I realized how much Mocha was holding me back. Mocha seems like a good enough tool until you realize its shortcomings:

Globals

Mocha implicitly depends on global variables like describe and it. Some assertion libraries even extend built-in prototypes! Implicit dependencies like this are confusing to new developers and make it a pain to properly lint your code without writing a bunch of exceptions.

AVA's dependencies are all explicit. Instead of writing:

describe('My Thing', () => {  
  it('should do a thing', () => {
    expect(true).toBeTrue();
  });
});

and praying that describe and it will be defined, instead you can explicitly declare your dependency to AVA at the top of your file and be sure it'll work:

import test from 'ava';

test('foo', t => {  
    t.true(true);
});

Aahhh. That's better.

Shared State

Most testing frameworks, Mocha included, provide beforeEach and afterEach hooks in addition to serial test running. The combination of serial tests and before/after hooks can sometimes encourage developers to share state between unit tests. This is a huge no no, because unit tests are supposed to be completely independent of one another. Shared state can cause tests to behave in unexpected ways.

AVA runs tests each test as a separate Node.js process. This means that you can change the global state of one test file without affecting another. AVA does allow you to run tests in serial if you have a good use case, but it can't be done by default. No more stateful gotchas.

Serial Test Running

Running things one after another is easy to understand for people who come from synchronous programming backgrounds. But JavaScript is async! There's no reason to force tests to wait for one another when JavaScript can run them concurrently. Mocha is slower than it needs to be.

AVA corrects this problem by running each test as a separate Node.js process. This means faster tests that take advantage of multi-core CPUs. The AVA devs provide a case study where switching from Mocha brought the testing time of a project from 31 seconds to 11 seconds.


Migration FAQ

How do I handle async code?

AVA is asynchronous by default. No more monkeying around with done().

import test from 'ava';

test('async test', t => {  
  return Promise.resolve(1)
    .then(result => {
      t.is(result, 1);
    });
});

It just works.

How do I setup mocking?

AVA doesn't have any built in mocking features, but mocking is trival to set up with Sinon.js. You can do all the same Sinon mocking with AVA that you can do with Mocha.

import test from 'ava';  
import sinon from 'sinon';

const myFunction = sinon.spy();

test('my function ran', t => {  
  myFunction();
  t.true(myFunction.called);
});
How do I report code coverage?

AVA unfortunately does not support vanilla istanbul due to the way it uses subprocesses. Thankfully you can just use NYC instead, which is a CLI wrapper for Istanbul with support for subprocesses. NYC is extremely easy to set up: just run npm i nyc --save-dev in your project and preface your test command with the nyc command.

{
  "script": {
    "test": "nyc ava"
  }
}

That's it. You'll get code coverage printed every time you run npm test, and NYC will also save the printout in a directory in your project's root.

How do I configure AVA to write tests in ES2015?

Its really easy. Just follow the steps in the README on how to hook up Babel and you're done.


Hopefully this convinces you to at least give AVA a try! Now that I've migrated my JavaScript projects to AVA, I can't see why I would choose Mocha again.