Tuesday, May 23, 2017

[JS] Unit testing with Karma and Jasmine

 karma    Jasmine    gulp  


▌Introduction


This is a simple sample for javascript unit-testing with karma, Jasmine and gulp.

karma :
A simple tool that allows you to execute JavaScript code in multiple real browsers.

A Behavior Driven Development testing framework for JavaScript.

Adapter for the Jasmine testing framework.



▌Sample codes




▌Implement


▋Install packages

▋karma
▋gulp


▋Create karma.conf.js

We can use the following command to create the karma.conf.js step by step, for more information, take a look at Karma : Configuration

$> karma init karma.conf.js

In this example, my test folders are as following,




So here is my karma.conf.js

Notice I used jquery in my javascript files, so the jquery library must be included in the files configuration.

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

        basePath: '',
        files: [
            // dependencies
            '../lib/jquery/dist/jquery.min.js',

            //Target js
            '../scripts/*.js',     

            //Test files
            'spec/*.spec.js',
        ],
        autoWatch: false, //Watching files and executing the tests if the files changes.
        singleRun: true //If true, Karma will run tests and then exit.
    });
};


▋Make a sample for testing

Assume that we would like to test the following html and script.

▋HTML

<div class="row">
    <div class="col-md-2">
        <input type="text" class="form-control" id="numA" />
    </div>
    <div class="col-md-2">
        <input type="text" class="form-control" id="numB" />
    </div>
    <div class="col-md-3">
        <input type="button" class="form-control" id="add" value="Add" />
    </div>
    <div class="col-md-3">
        <input type="button" class="form-control" id="minus" value="Minus" />
    </div>
    <div class="col-md-2">
        Result : <label id="result"></label>
    </div>
</div>



▋JS

'use strict';

$(function () {
    $('#add').click(function () {
        a = $('#numA').val();
        b = $('#numB').val();
        $('#result').text(add(+a, +b));
    });

    $('#minus').click(function () {
        a = $('#numA').val();
        b = $('#numB').val();
        $('#result').text(minus(+a, +b));
    });
})


function add(a, b) {
    return a + b;
}

function minus(a, b) {
    return a - b;
}





Where I put the html and js file to.




▋Create jasmine tests

We are going to write our first unit tests on functions, add and minus, to ensure that they works correctly.

First, add a new test file, *.spec.js




▋demo.index.spec.js

describe('Demo: function test', function () {

    it('should return 6 for 1 + 5', function () {
        expect(add(1, 5)).toEqual(6);
    });

    it('should return 2 for 6 - 4', function () {
        expect(minus(6, 4)).toEqual(2);
    });
});


For more Jasmine tutorials, go to jasmine.github.io.


▋Use gulp to start the test

Open gulpfile.js, add the following scripts to enable running test with gulp.

var gulp = require('gulp');
var server = require('karma').Server;

gulp.task('test', function () {
    new server({
        configFile: __dirname + '/wwwroot/test/karma.conf.js',
        singleRun: true
    }).start();
});


Then start the test by the following command,

$> gulp test

Succeed result:




Or fail result:



▌Handling HTML fixtures


▋Install packages

Preprocessor for converting HTML files into JS strings.

A plugin for the Karma test runner that loads .html and .json fixtures.



▋Update karma.conf.js

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

        basePath: '',
        files: [
            // dependencies
            '../lib/jquery/dist/jquery.min.js',

            //Target js
            '../scripts/*.js',     

            //Test files
            'spec/*.spec.js',

            //Inject html
            'html/*.html'
        ],
        preprocessors: {
            'html/*.html': ['html2js']
        },
        autoWatch: false,
        singleRun: true
    });
};


▋Refactor javascript

Before we create Jasmine test, we have to know how the preprocessor works.

This preprocessor converts HTML files into JS strings and publishes them in the global window.__html__, so that you can use these for testing DOM operations.

If we would like to simulate the click event on the 【Add】 or 【Minus】button in the tests, we need to re-add event listener on the DOM.

So let’s refactor the javascript and create an event handler to isolate the handler codes from event listener.


▋JS

'use strict';
$(function () {
    $('#add').click(function () {
        eventHandler(handler.add);
    });

    $('#minus').click(function () {
        eventHandler(handler.minus);
    });

})

let handler = {
    get add() {
        return "add";
    },
    get minus() {
        return "minus";
    }
};

function eventHandler(handler) {
    let a = $('#numA').val();
    let b = $('#numB').val();
    let rslt = 0;
    switch (handler) {
        case 'add':
            rslt = add(+a, +b);
            break;
        case 'minus':
            rslt = minus(+a, +b);
            break;
        default:
            break;
    }
    $('#result').text(rslt);
};
//Skip...




▋Create Jasmine tests

describe('Demo: Html test', function () {

    // inject the HTML fixture for the tests
    beforeEach(function () {
        fixture.base = 'html';
        fixture.load('demo.index.html');

        //Register events
        document.getElementById('add').addEventListener('click', function () { eventHandler(handler.add); });
        document.getElementById('minus').addEventListener('click', function () { eventHandler(handler.minus); });
    });

    // remove the html fixture from the DOM
    afterEach(function () {
        fixture.cleanup();
    });

    it('should return 10 for 4 + 6 on html', function () {
        document.getElementById('numA').value = 4;
        document.getElementById('numB').value = 6;

        document.getElementById('add').click();

        let actual = document.getElementById('result').innerHTML;
        expect(+actual).toEqual(10);
    });

    it('should return 2 for 6 - 4 on html', function () {
        document.getElementById('numA').value = 6;
        document.getElementById('numB').value = 4;

        document.getElementById('minus').click();

        let actual = document.getElementById('result').innerHTML;
        expect(+actual).toEqual(2);
    });

});


Run test again …





▌Reference





No comments:

Post a Comment