Start writing and testing ES6 code in the browser today with Traceur and Karma
ES6 has been a topic of discussion for a while now. Eventhough the final spec is expected to be released mid-2015, browser vendors are still faily behind as far as implementing these new features.
Today, we're going to learn how we can start not only writing, but also unit testing ES6 code that runs in most evergreen browsers, using Traceur and Karma.
You can find a complete working example of this tutorial in the form of a boilerplate here.
Prerequisites
This tutorial assumes that you have:
Node.js ( or io.js ) installed
An evergreen browser installed
Getting the goods
We'll need a few shims and polyfills to be able to run our es6 code. We can get these using Bower:
Traceur
ES6 Shim
ES6 Module Loader
System.js
Furthermore, we'll need a few more Node.js tools to handle the unit testing part, and spawn a quick web server to view our html pages. We can get these using NPM:
Karma
Mocha
Expect.js
Gulp
Express
Setting up the environment
Let's assume the following basic folder structure:
| lib/ |- src/ |- main.js | public/ |- css/ |- vendors/ | routes/ |- index.js | test/ |- specs/ |- fixtures/ | views/ | .bowerrc | bower.json | gulpfile.js | package.json | server.js
We'll put all our ES6 code lib the lib folder
We will have a main entry point file being lib/main.js
All classes will be placed in the lib/src subfolder
Tests will go in test/specs
Express views will be located in views/
Express routes will be defined in routes/index.js
The public/ folder will hold the vendors files downloaded via bower, along with any other static files we might need
Now, let's install the client-side dependencies:
bower install traceur es6-shim es6-module-loader system.js --save
And the Node.js dependencies:
npm install gulp karma express mocha karma-mocha expect.js karma-chrome-launcher --save-dev
Also, if you do not already have it, install the karma CLI:
npm install -g karma-cli
Writing and using our first ES6 class
Let's start by writing a very simple es6 class in lib/main.js:
export class MyClass { constructor() { console.log(this); } }
In order to load it in our web page, we first add the necessary dependencies:
<script src="/public/vendors/traceur/traceur.min.js"></script> <script src="/public/vendors/es6-shim/es6-shim.min.js"></script> <script src="/public/vendors/es6-module-loader/dist/es6-module-loader-sans-promises.js"></script>
Now, we can start using System.js to load our class. System.js can be configured to map specific files to shortnames. Let's create a system.config.js file and add the following:
System.paths = { 'main': '/lib/main.js', 'src/*': '/lib/src/*.js' };
In our html page, let's add a script tag at the bottom of the body and insert the following code:
System .import('main') .then(function(core) { var test = new core.MyClass(); });
You should now see MyClass {} outputed in your console. This is only the first step, let's now see how we can inherit from other classes. Let's create a file called test.js in lib/src/, and insert the following code:
export default class Test { constructor() {} add(a, b) { return a + b; } static ping(text) { return `It Works! You passed "${text}".`; } }
Let's analyse this code. First, we define a new class called Test, and we define two methods: an instance method called 'add', which simply adds two numbers, and a static method (also known as class method) called 'ping', which parses a template string with whatever text you pass as first argument. Notice how the template string is defined with a backtick ` and the text variable is interpolated using the ${} syntax.
Now, let's have our class Myclass extend from this new Test class:
import Test from 'src/test'; export class MyClass extends Test { constructor() { super(); } }
Again, let's analyse this code. We first import the Test class from the src/test folder (path is relative to current file). Then we declare MyClass to be extending Test, and finally we call the parent's class constructor by calling super();
Now, we can go back to our html, and use the methods from the Test class directly via our MyClass instance:
System .import('main') .then(function(core) { console.log(core.MyClass.ping('Hello, ES6 World!')); var i = new core.MyClass(); console.log(i.add(1, 2)); });
We can now make use of the methods from the Test class. The ping method can be called directly since it is a static method, while the add method requires MyClass to be instantiated first before we can call it.
And these are the basics of writing ES6 for the browser! We only covered a few of the new features of ES6; we strongly recommend you read the Babeljs docs for an extensive list of all the new es6 features. Let's now see how we can unit test this code we just wrote.
Unit testing ES6 classes with Karma
With a bit of extra configuraiton, we can easily start unit testing our ES6 classes using Karma, Mocha and Expect.js. The main two problems are the following:
We need to be able to pass some specific flags to Karma's Chrome launcher to enable harmony mode
We cannot directly include the ES6 classes as part of the file dependencies of the Karma config, otherwise the Chrome instance will throw an exception about 'illegal reserved word'.
Let's see how we can solve these problems.
Defining a custom Karma launcher
To pass specific flags to the Chrome instance, we can simply define a custom launcher in karma.conf.js:
customLaunchers: { 'Custom_Chrome': { base: 'Chrome', flags: ['--enable-javascript-harmony'] } }, // use the custom launcher browsers: ['Custom_Chrome']
And that's it for enabling the harmony mode in the Chrome instance! Let's see now how we can load our ES6 classes in our tests without causing an exception to be thrown.
Karma file patterns
The real issue here is that by default, adding the ES6 classes as part of the file dependencies in the Karma config means it will include the files directly in the page. A quick look at the Karma docs gives us the solution: make use of file patterns to only serve and watch the ES6 classes, but not include them. Here's how we proceed:
{pattern: 'lib/*.js', served: true, included: false, watched: true}, {pattern: 'lib/src/*.js', served: true, included: false, watched: true}
Now, we will be able to make use of System.js to load our ES6 classes asynchronously in a simple 'before()' hook in our Mocha tests!
Fixing the System.paths bug
There is one last problem: when trying to load the files in our tests using System.js, it still returns a 404 error. If you inspect the test page in the Chrome instance with the dev tools, you will quickly understand why: the Karma launcher prepends a '/base/' path in front of the 'real' path for the static files that it is including and serving. Therefore, the file mapping defined in our system.config.js file is now invalid.
The solution for this is to create a config file specifically for testing purposes. In test/fixtures, we simply create a file called system.test-config.js, and fill in the following:
System.paths = { 'main': '/base/lib/main.js', 'src/*': '/base/lib/src/*.js' };
Now, we simply add this config file to the list of file dependencies in our Karma config instead of the production config file (we can actually include this one directly), and the ES6 classes will be served properly when requested by System.js.
Writing our first test suite
Now we are ready to write our tests. We will using a combination of Mocha and Expect.js to get the job done. Let's start by loading our 'Test' class we created earlier into our test suite, by using System.js in our 'before()' hook:
var TestClass = null; before(function(done) { System .import('src/test') .then(function(t) { TestClass = t.default; //when exporting a default class, key is 'default' done(); }) .catch(function(e) { console.log('>>> ERROR loading the class', e); done(); }); }); after(function() { TestClass = null; });
We will also dispose of the class after the tests are done running. We can start testing both static and instance methods of our 'Test' class. For static methods, we can call them directly:
describe('The ping method', function() { it('can compile a template string', function() { var text = TestClass.ping('ES6'); expect(text).to.equal('It Works! You passed "ES6".'); }); });
For instance methods, we will simply need to create a new instance of the class before calling the method, as such:
describe('The add method', function() { var i; before(function() { i = new TestClass(); }); after(function() { i = null; }); it('can add two numbers', function() { expect(i.add(1, 2)).to.equal(3); }); });
Finally, we can simply run our test suite by typing karma start in our terminal at the root of our project.
You now have a tiny boilerplate allowing you to write and test ES6 code directly in the browser. Again, you can find a complete, working example here. The boilerplate is being regularily updated, so check out the master branch every once in a while for new features and polyfills!
We hope you found this tutorial useful; please let us know what you would like us to cover in the upcoming articles by reaching out to us on Twitter: @shortwavapp










