David Banks

Web Developer

Testing my custom DOM utility with Jasmine

20 June 2015JavaScriptTesting

This post explores how to use Jasmine to test my small custom DOM utility library. Jasmine is a Behaviour Driven Development (BDD) testing framework for JavaScript.

You can read about how I created my custom DOM utility in these 3 posts:
Build your own DOM utility part 1
Build your own DOM utility part 2
Build your own DOM utility part 3

Before we start, if you are new to Jasmine you should take a look at their documentation here. The 2 main Jasmine functions we will use in this post are describe and it. The former, describe creates a test suite, useful for grouping tests together into logical and organised blocks, and it creates a spec, or a test. Ideally, we want all tests within all suites to pass. This means our code is functioning as we expect it to.

Testing the $DOM() function

Because we are testing a set of DOM utility functions, I'll start by defining a function to wrap document.querySelectorAll, which returns the results as a standard JavaScript array instead of a NodeList (this is done using the Array slice method). This function which I've named qsa will come in handy for testing the element selection functionality of my custom DOM utility.



var qsa = function qsa(selector, root) {
    root || (root = document);
    return Array.prototype.slice.call(root.querySelectorAll(selector));
};

We'll begin by creating a suite which will contain tests for testing the main $DOM function, which can create new elements on the fly or select existing elements. Our first test will test if our utility creates new elements on the fly as expected:



describe('$DOM function', function() {

    // Test the $DOM function for creating new element

    it('should create new element', function() {

        var $el = $DOM('≤div>'),
            el = $el.get();

        expect(el instanceof Element).toEqual(true);
        expect(el.tagName).toEqual('DIV');

    });

});

Notice there are two expect functions in the test. The first says that we expect the the result of our $DOM call to actually be an object of class Element. The second expect says that we expect the new element to be a div element. When we pass a statement to Jasmine's expect function, we get back an object which we can chain other Jasmine matcher functions onto, such as toEqual, toBeLessThan, toContain, toBeDefined, toBeTruthy, and so on. We can even test for the inverse cases using assertions like expect(x).not.toEqual(0). For a test to pass, all assertions within it must evaluate to true.

Let's add a second test to the suite. We will test whether element selection functionality is working correctly or not. We'll test 3 different selectors to see if we get correct results. We compare our results with those returned from our qsa function.



it('should select elements matching specified selector', function() {
        
    var $div, div;

    // Select all divs
    $div = $DOM('div'),
    div = $div.map(function(d) { return d.get(); });
    expect(div).toEqual(qsa('div'));

    // Select all elements with the `test` class
    $div = $DOM('.test'),
    div = $div.map(function(d) { return d.get(); });
    expect(div).toEqual(qsa('.test'));

    // Select element with id `third-test`
    $div = $DOM('#third-test')[0];
    expect($div.get()).toBe(qsa('#third-test')[0]);

});

Testing the methods

Next we need to test all the methods defined in the $DOM utility. I use a suite for each method. As an example, let's test the hasClass() method:



describe('hasClass() method', function() {

    var $el;

    beforeEach(function() {
        $el = $DOM('.test')[0];
    });


    // Test on class that is known to be present

    it('should confirm presence of class', function() {
        expect($el.hasClass('test')).toEqual(true);
    });


    // Test on class that is known not to be present

    it('should confirm absence of class', function() {
        expect($el.hasClass('abc')).toEqual(false);
    }); 

});

As you can see, testing the hasClass() method is relatively straightforward. Let's test the data() method. This is one of the more complicated methods, in the sense that it can be called in a variety of ways to get different results. It can be used in the following ways:

Therefore there are 5 tests contained in the test suite for the data() method. Note we have to be careful here when we test object equality. Our $DOM utility uses "pure" objects to store data on elements, that is, objects with null prototype.



describe('data() method', function() {

    var $el;

    beforeEach(function() {
        $el = $DOM('#list-1')[0];
    });


    // Test setting a single fragment of data

    it('should set a single data fragment on the element', function() {
        $el.data('x', 1);
        expect($el.data('x')).toEqual(1);
    });


    // Test setting multiple data fragments at once

    it('should set an object of data fragments on the element', function() {
        $el.data({x: 5, y: 8, z: 13});
        expect($el.data('x')).toEqual(5);
        expect($el.data('y')).toEqual(8);
        expect($el.data('z')).toEqual(13);
    });


    // Test getting a single fragment

    it('should get a data fragment by name from the element', function() {
        expect($el.data('z')).toEqual(13);              
    });


    // Test getting all data fragments

    it('should get all data fragments set on the element', function() {
        var o = Object.create(null);
        o.x = 5; o.y = 8; o.z = 13;
        expect($el.data()).toEqual(o);
    });


    // Test removing all data fragments from the element

    it('should remove all data fragments from the element', function() {
        $el.data(null);
        expect($el.data()).toEqual(Object.create(null));
    });

});

At the time of writing there were 17 methods defined by the $DOM utility. To see tests for all the methods, download the code from GitHub (link at the top of the article). To run all test, open the SpecRunner.html file in the Jasmine directly. When all tests pass, we should see something like this:

All Jasmine tests passed

Whereas when one or more of our tests fail, we see something like the image below. Jasmine shows us which particular test(s) failed and where in our code we can find the assertions which did not evaluate to true.

All Jasmine tests passed