2017年5月23日 星期二

[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 Minusbutton 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





沒有留言:

張貼留言