Tuesday, November 12, 2013

TDD with Jasmine

Once I needed to rewrite one of my javascript objects completely, and the only thing I had was a javascript test specs file. Somehow the original code vanished but the test javascript file remained which I wrote some weeks ago. Ok, no problem, let us reimplement the object, at least it will be a good exercise.

Jasmine

The test was written in Jasmine which is a BDD unit testing framework for Javascript. The test looked like something similar:

describe("PubSub specs", function() {

    var ps;

    it("should create service", function() {
        ps = new PubSub();
        expect(ps).toBeDefined();
    });

    it("should subscribe listeners", function() {
        ps.subscribe('id1', 'event1', function() {});

        var listener = ps.getListeners('event1');
        expect(listener).toBeDefined();
        expect(listener.id).toEqual('id1');
        expect(listener.evt).toEqual('event1');
    });

});

It contained a specs with the describe function and it contained many test cases. It defined a variable ps, which was for holding the PubSub object instance. The different test cases checked something, like whether we can create the service object, or subscribe to specific events, etc.

Jasmine has a HtmlReporter so it can run unit tests in a html page. I won't paste here the boilerplate code, you can find how to setup in such a page in here in the Runner and Reporter part. For short, we need to create a simple html page, including the jasmine.js and jasmine-html.js files, the service file (pubsub.js) and the test (test-pubsub.js). Finally we need to be sure to call the HtmlReporter after the page is loaded. Then applying the default stylesheet, we will got this result.

So tests are failing, time to implement the service.

Coding

We can implement the service and check frequently what test cases are failing, what we manage to make fulfill.

function PubSub() {
}

This simple code makes the first test successful.

We are getting happier and happier as we can see our progress. It is funny but it works. We set small goals then we reach them. We set goals and so on... Sometimes it is tough to keep ourselves motivated, but with small wins I think it is doable.

function PubSub() {
    this.listeners = [];

    this.subscribe = function(id, target, callback) {
        for (var i = 0; i < this.listeners.length; i++) {
            var l = this.listeners[i];
            if (l.id == id) {
                // Already subscribed
                l.evt = target;
                l.callback = callback;

                return;
            }
        }
        // Not subscribed yet
        this.listeners.push({
            id: id,
            evt: target,
            callback: callback
        });
    };
}

Refresh the browser and you can see that everything is fine. (I won't show it, you need to try it by yourself :) .) It is not perfect because we implemented too much, so let us go back and write some tests for checking whether the callback is the same what we passed in the 3rd argument.

I like writing unit tests for Javascript because there is no type spec of variables they can accept values of any type. So it defines expectations we can hardly live without. For examples for return values, what properties of the result object needs to have.

Node.js and Karma

If you are using frameworks (jQuery, angular.js) and you still want to have unit tests, you can setup Node.js and Karma module to run the tests. It is more comfortable because every time you save either your code or your unit test, the tests will be run again. So you can see the console window and can see your progress.

The only thing you need to write is a simple config file. You can get a good default and taylor to your needs. I am using the two mentioned frameworks, so I list them in the files config attribute. I set Jasmine as a framework, so I didn't even need to download Jasmine sources to run the tests.

module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine'],

    files: [
      'priv/js/lib/jquery.js',
      'priv/js/lib/angular.js',
      'priv/js/lib/angular-mocks.js',
      'priv/js/pubsub.js',
      'test/js/**/*.js'
    ],

    exclude: [
      '**/*.swp'
    ],

    reporters: ['progress'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    captureTimeout: 60000,
    singleRun: false
  });
};

Jasmine know much more, so I will present some good practices next time.

Share:

0 comments :

Post a Comment

Richard Jonas. Powered by Blogger.

About me

My name is Richárd Jónás, live in Budapest, Hungary. In this blog I want to share my coding experiences in Erlang, Elixir and other languages I use. Some topics are simpler ones but you can use them as a reference. I also present some of my thoughts about developing distributed systems.