Using DataProvider for Jasmine


When it comes to writing comprehensive tests we always need a sort of “data provider” necessary to execute the tests several times with a different input. The test methods (like it) should be written, of course, just once.

There is a very good article providing a good solution:
http://blog.jphpsf.com/2012/08/30/drying-up-your-javascript-jasmine-tests

Nevertheless, I find the TestNG way to deal with it very good because there is a clear separation between the test method and the used data provider.

If you aren’t familiar with TestNG look at this example: http://testng.org/doc/documentation-main.html#parameters-dataproviders (Note: TestNG is actually a Java Testing Framework and it’s more powerful than JUnit).

I like defining the “data provider” immediately before the actual test call. Example:

    using([1,2,3],[3,2,1]);
    it('sum should be 6', function() {
        var c = new Calculator();
        expect( c.sum.apply(this,arguments) ).toBe(6);
    });

The name of the function to be used as the data provider is using (thanks for this idea to https://twitter.com/jphpsf).

Sometimes, the data can become very huge. Then, a separated function would come pretty handy. It could populate the data from the server or the localStorage. Anyway, it provides more possibilities and therefore makes the tests more robust. For example:

    using("big_data"); //"big_data" is the name of the function which will return the test data.

    //this test method will be called 20 times...
    it('sum should be 6', function() {
        var c = new Calculator();
        expect( c.sum.apply(this,arguments) ).toBe(6);
    });

The nested function function() { var c = new Calculator(); ... } (which is actually the method we are going to test) needs to be called several times depending on the number of records returned by “big_data”.

We can reimplement the it method and inject it this way: it_multi will expect a reference to the test data called data. While looping through the records contained inside data we remove each one and use it to call the it function. Additionally, we extend the test description by appending the particular arguments.

The new method using cares for feeding some test data for the next it invocation. It supports defining test data directly and by a separated function as well.

//-----  Jasmine overrides

var it_multi = (function() {
    return function _it_multi(desc, func) {
        //var fun = arguments.callee; //is deprecated since ECMAScript5
        var fun = _it_multi;
        //-- check for array
        if( !fun.data
            || Object.prototype.toString.call(fun.data) !== '[object Array]'
            || fun.data.length == 0 )
            fun.data = [fun.data || null];
        //-- check for 2-dimensional array
        if( Object.prototype.toString.call(fun.data[0]) !== '[object Array]' )
            fun.data = [fun.data];
        var specs = [];
        while( fun.data.length > 0 ) {
            var _data = fun.data.pop();
            var _desc = desc + " using parameters [" + _data.toString() + "]";
            specs.push( jasmine.getEnv().it(_desc, (function(self, func, _data) {
                return function() {
                    func.apply( self, _data);
                }
            })(this, func, _data)) );
        }
        return specs;
    }
})();

if( it && typeof it == 'function') {
    it = it_multi;
}

var using = (function() {
    return function() {
        var data;
        if(typeof arguments[0] == 'undefined')
            data = [[]];
        if(typeof window[arguments[0]] == 'function') {
            var fun = window[arguments[0]];
            data = fun.call();
        }
        return (it.data = data || Array.prototype.slice.apply(arguments,[0,arguments.length]));
    }
})();

//--------------------

The class we are going to test is a simple calculator implementation:

function Calculator() {
//....
    this.sum = function() {
        var s = 0;
        for( var i=0;i<arguments.length;i++)
            s += arguments[i];
        return s;
    }
//....
}

The according tests are here:

   function big_data() {
        return [
            [1,2,7],
            [1,2,3,4],
            [1,1,1,1,1,1,1,1,1,1],
            [2,1,2,1,2,1,1],
            [4,4,2],
            [3,3,3,1],
            [2,2,2,2,2],
            [5,5],
            [2,8],
            [9,1],
            [4,2,2,1,1]
        ];
    }

    //using nested data
    using([1,2,3],[3,2,1]);
    it('sum should be 6', function() {
        var c = new Calculator();
        expect( c.sum.apply(this,arguments) ).toBe(6);
    });

    //using huge data by function
    using("big_data");
    it('sum should be 10', function() {
        var c = new Calculator();
        expect( c.sum.apply(this,arguments) ).toBe(10);
    });

    //no data at all. just to check if we can deal with no parameters 😉
    using();
    it('sum should be 0', function() {
        var c = new Calculator();
        expect( c.sum.apply(this,arguments) ).toBe(0);
    });

The result of the test:

sum should be 6 using parameters [3,2,1]	16 ms
sum should be 6 using parameters [1,2,3]	3 ms
sum should be 10 using parameters [4,2,2,1,1]	2 ms
sum should be 10 using parameters [9,1]	3 ms
sum should be 10 using parameters [2,8]	1 ms
sum should be 10 using parameters [5,5]	2 ms
sum should be 10 using parameters [2,2,2,2,2]	1 ms
sum should be 10 using parameters [3,3,3,1]	4 ms
sum should be 10 using parameters [4,4,2]	3 ms
sum should be 10 using parameters [2,1,2,1,2,1,1]	2 ms
sum should be 10 using parameters [1,1,1,1,1,1,1,1,1,1]	1 ms
sum should be 10 using parameters [1,2,3,4]	3 ms
sum should be 10 using parameters [1,2,7]	2 ms
sum should be 0 using parameters []	1 ms
Advertisements

5 thoughts on “Using DataProvider for Jasmine

  1. Hi Katarina,

    Thank you very much for this helpful article. I followed your instruction step-by-step and get this error “window is not defined” when it run at if(typeof window[arguments[0]] == ‘function’) {…}
    Would you mind give me some tips to overcome this? It would be great!

    Thanks,
    Quoc

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s