Skip to content

Instantly share code, notes, and snippets.

@fredrikbonander
Created February 25, 2014 15:36
Show Gist options
  • Save fredrikbonander/9211293 to your computer and use it in GitHub Desktop.
Save fredrikbonander/9211293 to your computer and use it in GitHub Desktop.
unit-testing-js

If you are unfamiliar with the concepts of TDD (test-driven-development) there's a vast sea of information about it. Just use your favourite search engine and jump in.

The concept it self is very simple. First write you tests then write code that will be tested.

In reality this is hard. It's hard because is like thinking backwards. It's hard because you might not always now what something need to do or how the specific API works. You need to experiment first. Do not fear, TDD and unit testing will help you. One of the most important things to know is that TDD is not a ruleset, it's a tool. And you'll soon find that your code is more decoupled and more awesome.

Setting up a testrunner for testing:

Before running your tests you need somewhere to run the tests. Here Karma is your friend. Karma is easily installed with npm:

$ npm install -g karma

To setup karma go to your app root:

$ karma init my.conf.js

Answer all the questions (choose jasmine when promted for testing framework). And you are set.

Unit testing in JavaScript with Jasmine:

Jasmine is a testing framework for JavaScript. It has alot of nifty features. The most common things you'll use is:

  • describe Describes a testsuite
  • it A test spec
  • expect Matcher to determine if a test fails or not

Imagine this scenario, we have a http service we want to test. The implementation and usage is simple:

// http.js
function http() {
    function query(method, url, callback) {
        var urlStr = '/' + url.entity + '/' + url.id,
            req = new XMLHttpRequest();

        req.open(method, urlStr, true);
        req.onload = callback;
        req.send();
    }

    return {
        get: function (url, callback) {
            return query('get', url, callback);
        }
    }
}

var response;
http().get({entity: 'user', id: 42}, function (res) {
    response = res.data;
});

And we want to test that .send() is invoked on an instance of XMLHttpRequest and is return with a response from the server. We mock the XMLHttpRequest so we can control the response. Read more about spies here.

To test this your test might look like:

// http.spec.js
describe('http service', function () {
    /* Mock for XMLHttpRequest. */
    spyOn(window, 'XMLHttpRequest').andCallFake(function() {
        return {
            open: function () {},
            send: function () {
                this.onload({data: {name: 'Scrooge'}});
            }
        };
    });

    it('should call .send of XMLHttpRequest and pass response to callback', function () {
        var response;
        http().get({entity: 'user', id: 42}, function (res) {
            response = res.data;
        });

        expect(respone).toEqual({name: 'Scrooge'});
    });
});

Now if you modify you http service and cause some breaking changes your test will fail and you know you broke something.

Looking at our code a next step would probably be to write to make sure that the url is compiled in the correct way. The problem is that the url is compiled inside the query method. And with single responsability in mind this should actually be it's own method. So first lets add a test for it.

// http.spec.js
describe('http service', function () {
    ...

    it('should compile url string from object', function () {
        var url = {entity: 'user', id: 42};
        expect(http().compileUrl(url)).toBe('/user/42');
    });
});

This will fail since we don't have an method called compileUrl in http. Let's add it:

// http.js
function http() {
    function query(method, url, callback) {
        var req = new XMLHttpRequest();
        req.open(method, url, true);
        req.onload = callback;
        req.send();
    }

    return {
        compileUrl: function (url) {
            return '/' + url.entity + '/' + url.id;
        },
        get: function (url, callback)
            return query('get', this.compileUrl(url), callback);
        }
    }
}

Now the methods inside http are smaller and only does a single thing. Kinda.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment