JavaScript TDD with Jasmine and Karma

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:

$ npm install -g karma-cli
$ npm install -g phantomjs
$ npm install karma-jasmine --save-dev
$ npm install karma-phantomjs-launcher --save-dev

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)

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();

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

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
});

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:

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

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.

    //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');
        });
    });

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:

    //replace “//will add new functionality here later” with the following:
    append: function append(string1, string2) {
        return string1 + ' ' + string2;
    }

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

25 thoughts on “JavaScript TDD with Jasmine and Karma

  1. Thanks Man! The best tutorial on Karma & Jasmine. To make TDD even faster it would be great to use Karma + Jasmine inside IDE in order to get tests ouput in the built-in terminal. Hope latest Netbeans 7.4 makes this possible.

    Like

  2. This was an extremely helpful article and yes I conducted the exercise in IntelliJ’s webstorm. It was very easy to use. I will post an article on some of the extra settings I did with Grunt as well.

    Like

  3. I am using WebStrom 7.0.3 IDE on Windows 7 platform. I am using phantomjs as browser. Now when I run using ‘Run’ command from the IDE, karma server is starting and connecting to socket and it runs the test cases.

    But if I open the terminal window in Webstorm IDE and type karma start karma.conf.js then karma server is starting and connecting to socket but does not run the test cases. Why is that?

    Like

  4. Jason, great great writeup, got me started in a very good direction! Thanks!!
    I am trying to use mocha instead of Jasmine for the client tests as well and I am failing spectacularly…. any chance you can point me in the right direction??

    Like

  5. Great article, thanks! Only thing you should fix is that your append function returns ‘helloworld’ and not ‘hello world’, so actually the last test fails.

    Like

    • I got a similar error. So removed space ‘hello ‘ here:
      expect(myfunc.append(‘hello ‘,’world’)).toEqual(‘hello world’);

      Like

  6. 好文章。Thank you ! Good post! But when I am trying to run with PhantomJS, the terminal show:

    Error: Cannot start PhantomJS
    Error: spawn EACCES

    Mac OS X 10.9.2, Nodejs 0.10.24, Karma 0.12.15, PhantomJS 1.9.7

    Like

    • Hey, I got it to work by installing these two:

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

      Like

      • Hey Thanks for sharing! I think this might be new as I too recently discovered this when working with a coworker that was having issues getting karma to run tests properly. Installing the 2 additional karma plugins fixed his issue as well. Im updating the article to reflect this. Thanks again!

        Like

  7. Hi ,

    I had to write automated scripts to do unit testing using Angular js. Can anyone please help me in learning basics on how to start and run the scripts.
    Thank You

    Like

  8. I think I have followed all the steps, but something is missing.

    Can you suggest why on the very first run I get :

    TypeError: undefined is not a function at Object. (/home/yourself/projects/gettingstarted-karma/js/tests/object.test.js:5:31)

    line #5 has : spyOn(myfunc, ‘init’).andCallThrough();

    Like

    • First guess is that init simply doesnt exist on myfunc, which might mean that myfunc isnt being declared properly within the test (or associated file it actually exists in). Can you post more of the test suite code block so I can see some context of the error.

      Like

      • I had the same problem, and changing .andCallThrough() from the post to .and.callThrough() is what fixed it, like you said. Thanks!

        Like

  9. This is a great walkthrough, and I thank you for publishing it. There is one thing I think might help other users. At first when I ran the test suite, I was getting an error “require not defined.” This turned out to be a little rabbit hole whereupon I was investigating all measure of node_modules and the order they were included in the files. As it turns out, and I don’t know if it is my particular configuration, the resolution was to answer yes in the karma init file to add the Require.js plugin. Once that was fixed, it worked just like you said it would.

    Like

    • Thanks! This post is pretty old actually, and Im going to be working on a 2.0 version of this article in the very near future, as well as giving a talk on it at All Things Open this year in October!

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s