We do rapid releases at AWeber so we’re constantly pushing code changes to our users. To be sure that we aren’t in a constant state of “fix 1 bug, introduce 2 more” we embrace TDD across our entire stack. This includes making sure our front-end JavaScript code has thorough test coverage.

A very nice combination that our team has embraced is Jasmine for writing our tests and the Karma (a.k.a. Testacular) test runner for actually running them.

Prerequisites:

As with most things lately, you’ll want to make sure you have node.js and npm installed.  If you don’t already have node.js installed, you can download it at http://nodejs.org.

From a command-line, you want to install Karma and PhantomJS using npm:

[code lang=text]

$ npm install -g karma-cli

$ npm install -g phantomjs

$ npm install karma-jasmine –save-dev

$ npm install karma-phantomjs-launcher –save-dev

[/code]

A basic project to run some tests:

Before we get started actually writing some tests, let’s first make sure we have a basic project that we can test against. Create the following folder structure:

/js/object.js

/js/tests

/js/tests/object.test.js

First we will create a basic JavaScript object that we can run some tests against: (js/object.js)

[sourcecode lang=”javascript”]

if (typeof NS == ‘undefined’) { NS = {}; }

NS.myFunction = {

//empty stuff array, filled during initialization

stuff: [],

init: function init() {

this.stuff.push(‘Testing’);

},

reset: function reset() {

this.stuff = [];

},

//will add new functionality here later

};

NS.myFunction.init();

[/sourcecode]

Now lets create some basic code for our Jasmine tests: (js/tests/object.test.js)

[sourcecode lang=”javascript”]

describe(“myFunction”, function() {

var myfunc = NS.myFunction;

beforeEach(function(){

spyOn(myfunc, ‘init’).andCallThrough();

});

afterEach(function() {

myfunc.reset();

});

it(“should be able to initialize”, function() {

expect(myfunc.init).toBeDefined();

myfunc.init();

expect(myfunc.init).toHaveBeenCalled();

});

it(“should populate stuff during initialization”, function(){

myfunc.init();

expect(myfunc.stuff.length).toEqual(1);

expect(myfunc.stuff[0]).toEqual(‘Testing’);

});

//will insert additional tests here later

});

[/sourcecode]

Configure Karma for the project:

Now that the project is setup, lets configure Karma to work with it. Karma requires a configuration file in order to run, but fortunately includes an automatic initialization process that will create one for you:

$ karma init karma.conf.js

This will present you with a number of questions. Provide the following answers:

[sourcecode light=”true”]

Which testing framework do you want to use ?

Press tab to list possible options. Enter to move to the next question.

jasmine

Do you want to use Require.js ?

This will add Require.js plugin.

Press tab to list possible options. Enter to move to the next question.

no

Do you want to capture a browser automatically ?

Press tab to list possible options. Enter empty string to move to the next question.

Chrome

What is the location of your source and test files ?

You can use glob patterns, eg. “js/*.js” or “test/**/*Spec.js”.

Enter empty string to move to the next question.

js/*.js

js/tests/*.js

Should any of the files included by the previous patterns be excluded ?

You can use glob patterns, eg. “**/*.swp”.

Enter empty string to move to the next question.

Do you want Karma to watch all the files and run the tests on change ?

Press tab to list possible options.

yes

[/sourcecode]

Once you have completed the questions, a karma.conf.js file will be created and should exist in the root of the project.

Running the tests and doing some TDD:

Assuming everything has gone according to plan so far, we should be able to run Karma and see the results of the first tests:

$ karma start karma.conf.js

Screen Shot 2013-11-19 at 3.21.36 PM

One thing to note is that we have setup our Karma configuration file to use Chrome as the browser. So when you start Karma, it will launch a new instance of Chrome that is attached to the runner. The runner will load all of the JavaScript files specified in the configuration file into the browser so that they can execute in a true DOM environment. Later we will reconfigure Karma to run using PhantomJS instead.

Great, now we have a very basic test running against our JavaScript object. Lets add another test for some new functionality:

Edit the js/tests/object.test.js file to include a new test for the new functionality.

[sourcecode lang=”javascript”]

//replace “//will insert additional tests here later” with the following:

describe(“appending strings”, function() {

it(“should be able to append 2 strings”, function() {

expect(myfunc.append).toBeDefined();

});

it(“should append 2 strings”, function() {

expect(myfunc.append(‘hello ‘,’world’)).toEqual(‘hello world’);

});

});

[/sourcecode]

Obviously this new test will fail because we haven’t actually written the functionality yet. If you kept the Karma runner open, you will see that once you saved the object.test.js file Karma will notice that and instantly rerun the tests. This is because in the configuration file we set autowatch to true – which means Karma will watch for changes to any of the files listed in the files section.

Screen Shot 2013-11-19 at 3.24.16 PM

There we go, the tests failed as we expected. Now lets edit js/object.js and actually include the new functionality we are testing for:

[sourcecode lang=”javascript”]

//replace “//will add new functionality here later” with the following:

append: function append(string1, string2) {

return string1 + ‘ ‘ + string2;

}

[/sourcecode]

Taking another look at the Karma output, it should have rerun again and we should now have all of the tests pass!

Screen Shot 2013-11-19 at 3.26.08 PM

Using PhantomJS instead of Chrome:

Earlier when we ran Karma it launched a dedicated Chrome window. This is fine for local development, but in a server environment where you want to automate your build process and include the test run, you don’t want it launching a Chrome window every time (nor will it even be possible typically). Thats where PhantomJS comes in. PhantomJS is a headless browser which basically means it runs and interprets HTML and JavaScript without the overhead of an actual window.

Edit the karma.conf.js file and update the line that lists Chrome as the browser and change the value to PhantomJS. The browsers setting is an array, so you could include multiple browsers if you are testing against heavy DOM manipulation and you want to be sure your tests pass across the board.

Note: Chrome and PhantomJS support are included with Karma, additional browsers will require an extra Karma-Browser specific plugin.

Start up karma again and this time you will notice that it runs as usual and executes the tests (and they pass) but it never launched that dedicated Chrome window.

Screen Shot 2013-11-19 at 3.27.58 PM

Note: In addition to using PhantomJS when running your tests in a build process, you will also want to turn autowatch off so that Karma runs the tests and completes in only a single run. In the output above, I skipped editing the karma.conf.js file and just used command line options instead.

TDD made easy makes TDD fun!

The ease with which you can write tests using Jasmine and run them using Karma makes doing TDD, dare I say it, fun! You can run Karma in autowatch mode, and let it sit in a window and watch away. Then you write tests, save and fail. Write code, save and pass. Rinse and repeat! In the end you’re writing better code!

Happy testing!

The complete code for this article can be downloaded from this repo:

https://github.com/jkat98/gettingstarted-karma