Setting Up JavaScript Testing Tools for ES6

Using ES6 (and even far future versions like ES7!) is becoming very easy these days – just set up Babel, and you’re off to the races. If you’re only writing code for NodeJS, you might even get away without Babel, as the native ES6 support is getting very good.

The workflows are easy and detailed for development, but what about testing? How do you write unit tests for ES6 code? How do you even configure the testing tools to work with the new features?

In this article, I’ll show you how to configure the most popular testing tools – Mocha, Jasmine, Karma, and Testem – to work with ES6. We’ll also go over some best practices for testing ES6 code and writing tests with ES6. You won’t have to neglect testing your ES6 code anymore!

Prerequisites

Before we start, we need to install some necessary tools:

We need Babel and some related libraries to compile ES6 code

And we need Webpack or Browserify to bundle our modules together

Even if your project has these set up already, you should check the sections below. Some of the testing tools may require additional libraries which you’ll otherwise miss.

Installing Babel and its libraries

Regardless of which testing tool or bundler you use, we need Babel and babel-polyfill. As you might know, Babel itself converts the new ES6 syntax to something older JavaScript engines understand, and babel-polyfill adds the missing objects, such as Promise, and new functions, like Array.prototype.find.

npm install --save babel babel-polyfill

If you intend to test in Node.js, you’ll also need babel-register. This module allows you to automatically compile code with Babel with your testing tool of choice.

npm install --save babel-register

You may also need to install your Babel presets of choice, such as es2015 and react.

npm install --save babel-preset-es2015 babel-preset-react

Configuring Babel

Although you can pass Babel configuration options on the command-line to many tools, or put them into your package.json file, I recommend using a .babelrc file instead.

Babel automatically picks up settings from .babelrc. This happens even if you use a tool, which then calls Babel for you. Putting your configuration options into .babelrc means you don’t need to maintain the settings in multiple locations.

Here’s an example .babelrc file when using the es2015 and react presets:

{
"presets": ["es2015", "react"]
}

Setting up Webpack or Browserify

NodeJS user note: If you only intend to test code with Node, you don’t need to do this step. You can skip straight to the section discussing your testing tool of choice.

Browserify users should install the babelify transform library. This allows Browserify to transform the code using Babel during the bundling process.

npm install --save babelify

You can either pass it as a parameter for browserify, using…

browserify -t [ babelify ] some-file.js -o some-output-file.js

…or set it up in package.json:

"browserify": {
"transform": ["babelify"]
}

For webpack, you need to install babel-loader, which is used by Webpack to compile code with Babel.

The order of loading files here doesn’t matter, as long as you include the bundled file before calling mocha.run(). If you use require within your tests to load any assertion libraries or other tools you’re using, you don’t need to include their scripts in the runner either.

Jasmine

For Node.js, Jasmine is unfortunately not an ideal choice. Although it works, setting up Babel for it is a bit hackier than for Mocha.

Unlike Mocha, Jasmine doesn’t give us a command-line flag for compilation. Therefore, we need to run Jasmine using babel-node. The babel-node tool is a wrapper around node, which runs your code through Babel.

To make running Jasmine with it easier, we’ll also install Jasmine to our local node_modules directory.

npm install -g babel-clinpm install jasmine

In order to make Jasmine work, you need to first initialize its configuration file using…

node_modules/.bin/jasmine init

This creates the file spec/support/jasmine.json which you can modify to configure things such as the location of your test files.

We can then run our Jasmine specs with Babel using…

babel-node node_modules/.bin/jasmine

It is a bit of a mouthful, and as usual, you can set it up as an npm script among other options.

"scripts": {
"test": "babel-node node_modules/.bin/jasmine"
}

When testing in browser, Jasmine’s steps are the same as for Mocha. Use your favorite module bundler to generate the ES6 output for your tests, and include the tests in the spec runner file.

Browserify: browserify test/**/*.js -o tests-bundle.js

Webpack: webpack test/**/*.js tests-bundle.js

Karma

With Karma, to run Babelified tests in browsers, we need to include the karma-babel preprocessor, which allows Karma to compile things with Babel.

npm install karma-babel-preprocessor

Once installed, you can add the following configuration options to your Karma configuration file:

The serve_files option tells testem about any additional files it should serve to the browser for testing. Since we use the before_tests option to bundle our code into the file tests-bundle.js, we include it with serve_files

The above snippet demonstrates ES6 features in tests. We’re using const instead of var when loading Chai. This means we can’t accidentally re-define things, and it also communicates our intention that it should never change.

We also make use of arrow functions. You can slightly simplify the code with them, however, they do come with some possible issues which we’ll look at in the best practices part of this article.

And lastly, similar to loading Chai, we use const to declare the expected result variable. This can again avoid problems, and it clarifies the intention of the value never changing.

We can run the above test using Mocha, with the command shown earlier in the article:

mocha --compilers js:babel-register --require babel-polyfill

Async tests

Writing asynchronous tests with arrow functions works the same way as before, by passing the done callback:

Best Practices

There are some potential ES6-specific best practices and gotchas you may run into, which we’ll look at below.

Take care with arrow functions in Mocha

Be careful with arrow functions and Mocha. In some cases, you may need to use this.timeout for example to control how long a test will wait before timing out. Using arrow functions, it won’t work.

The reason for this is how arrow functions work with this. As a result, Mocha can’t bind its helper functionality correctly. If you don’t need to use any of the helpers, using arrow functions is OK.

Avoid arrow functions with Sinon

Similar to Mocha, Sinon.js can be problematic in some cases with arrow functions.

The problem is sinon.test. It’s a good idea to use this when using test doubles within your tests, as it avoids problems with restoring the doubles at the end of the test. But, it uses this bindings, and because of the special this behavior with arrow functions, it doesn’t work correctly with arrow functions.

Either don’t use arrow functions when using sinon.test, or initialize and restore a sandbox manually in beforeEach and afterEach.

Webpack: you can enable source maps by including devtool: 'source-map' in your webpack configuration file

If you’re running your tests from the command line, source maps are unnecessary.

Conclusion

Testing ES6 code is easy; it just requires a little bit of configuration for the tooling. In the future with improved ES6 support, you can do away with these, unless you want to make use of Babel for some other purpose (such as supporting ES7).

Writing tests with ES6 is mostly the same as writing tests without it. Just keep in mind that arrow functions can be problematic, and you’re good.

So which tool should you use? I recommend Mocha. It has the best support for ES6 testing, thanks to its native support for promises. It also works nicely with the existing tools and libraries.