Search code examples
javascriptnode.jsunit-testinggruntjskarma-mocha

Variable success in running unit tests - Node, Mocha, Chai, Karma, Grunt


I have a test suite that I've built around my JavaScript application code containing ~350 tests. My tests are run by running grunt test from the node command line. Each time the tests run, a different amount of my tests run before it stops. It rarely finishes all of my tests. Has anyone had this kind of thing happen before? Any thoughts on what could cause it?

When I run the tests, I usually get a seemingly random number from 270 - 350 test completed. After the test, I don't get any errors or anything. It ends saying something like:

Finished in 1.332 secs / 0.823 secs

Summary:
√ 311 test completed

Here is an example of one of the tests that rarely gets run:

describe('util.js', function() {
    it('should create the global SeniorHomes object', function() {
        expect(sh).to.be.an('object');
    });
    describe('sh.cookies.create', function() {
        sh.cookies.create('name', 'value', 10);
        it('should create a cookie', function() {
            var cookies = document.cookie.split('; '),
                cookie;
            $.each(cookies, function(i, item) {
                if (item.split('=')[0] === 'name') {
                    cookie = {
                        name: item.split('=')[0],
                        value: item.split('=')[1]
                    };
                }
            });
            expect(cookie.name).to.equal('name');
            expect(cookie.value).to.equal('value');
        });
    });
    describe('sh.cookies.read', function() {
        sh.cookies.create('name2', 'value2', 10);
        var cookie = sh.cookies.read('name2');
        it('should read the value of a cookie', function() {
            expect(cookie).to.equal('value2');
        });
    });
    describe('sh.cookies.erase', function() {
        sh.cookies.create('name3', 'value3', 10);
        sh.cookies.erase('name3');
        it('should delete a cookie', function() {
            expect(sh.cookies.read('name3')).to.equal(null);
        });
    });
    describe('sh.format_time', function() {
        var date1 = new Date('01/31/2005 10:00'),
            date2 = new Date('01/31/2005 15:59'),
            output1 = sh.format_time(date1),
            output2 = sh.format_time(date2);
        it('should output a formatted time string', function() {
            expect(output1).to.equal('10:00 am');
            expect(output2).to.equal('3:59 pm');
        });
    });
    describe('sh.to_title_case', function() {
        var output = sh.to_title_case('test_string');
        it('should output a title formatted string', function() {
            expect(output).to.equal('Test String');
        });
    });
    describe('sh.to_snake_case', function() {
        var output = sh.to_snake_case('Test String');
        it('should ouput a snake cased string', function() {
            expect(output).to.equal('test_string');
        });
    });
    describe('copy_object', function() {
        var original = {
            test1: {
                test2: 'test3'
            }
        };
        var copy = sh.copy_object(original);
        it('should create a copy of the original object', function() {
            expect(copy.test1.test2).to.equal(original.test1.test2);
        });
    });

});

And here is the JavaScript that it is testing:

/**
 * Name: Util.js
 * Description: Contains javascript used on both frontend and backend
 * Note: jQuery Dependent
 */

/**
 * Global namespace for site functionality
 * @type {Object}
 */
if (!sh) {
    var Site = function() {},
        sh = new Site();
}

$(function() {
    //apply calendar datepicker (jquery ui) to input
    $('.datePicker').datepicker();
    $('.datestampPicker').datepicker({
        dateFormat: "yy-mm-dd 00:00:00"
    });

    //Alert confirm message for .confirm buttons
    $('.confirm').click(function(e) {
        var confirmation = confirm('Are you sure you want to do this?');
        if (!confirmation)
            e.preventDefault();
    });});

sh.environment = (function() {
    var subdomain = window.location.host.split('.')[0];
    if(subdomain === 'www') {
        return 'production';
    } else {
        return 'development';
    }
})();

sh.cookies = {};
/**
 * Create a cookie
 * @param  {string} name  name of cookie
 * @param  {string} value value of cookie
 * @param  {int} days  expiration length
 * @return {null}       no return value
 */
sh.cookies.create = function(name, value, days){
    var expires = '';
    if (days) {
        var date = new Date();
        date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
        expires = "; expires=" + date.toGMTString();
    }

    document.cookie = name + "=" + value + expires + "; path=/";
};

/**
 * Read the value of a cookie
 * @param  {string} name name of cookie
 * @return {string|null}      value of cookie
 */
sh.cookies.read = function(name){
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for (var i = 0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') c = c.substring(1, c.length);
        if (c.indexOf(nameEQ) === 0) {
            return c.substring(nameEQ.length, c.length);
        }
    }
    return null;
};

/**
 * Erase a cookie
 * @param  {string} name name of cookie
 * @return {null}      no return value
 */
sh.cookies.erase = function(name){
    sh.cookies.create(name, "", -1);
};

/**
 * Get a formatted time from a JavaScript date time object
 * @param  {object} time date time
 * @return {string}      formatted date time (hh:mm pm/am)
 */
sh.format_time = function(time){
    var minuteFormat = time.getMinutes() < 10 ? '0' + time.getMinutes() : time.getMinutes();
    var timeString =
        time.getHours() - 12 > 0 ?
        time.getHours() - 12 + ':' + minuteFormat + ' pm' : time.getHours() + ':' + minuteFormat + ' am';

    return timeString;
};

/**
 * Formats a string to capital letters with spaces
 * @param  {string} str string to be title-cased (test_string)
 * @return {string}     title case version of str (Test String)
 */
sh.to_title_case = function(str){
    return str.replace(/_/g, ' ').replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
};

/**
 * Formats a string to lower case with underscores instead of spaces
 * @param  {string} str string to be snake cased (Test String)
 * @return {string}     snake case version of str (test_string)
 */
sh.to_snake_case = function(str){
    return str.toLowerCase().replace(/ /g,'_');
};

/**
 * Produces a copy of a javascript object
 * @param  {object} object object to be copied
 * @return {object}        copy
 */
sh.copy_object = function(object) {
    return JSON.parse(JSON.stringify(object));
};

//returns the version of ie as integer
sh.get_ie = (function() {
    var myNav = navigator.userAgent.toLowerCase();
    return (myNav.indexOf('msie') != -1) ? parseInt(myNav.split('msie')[1]) : false;
}());

/**
 * Various data structures
 * 
 * @type {Object}
 */
sh.structures = {};

/**
 * A general exception
 */
sh.structures.Exception = Error;

Solution

  • When I run the tests as a single run, it would say aborted due to warnings. There were some warnings about the some tests doing a full page reload. So, I removed tests for two functions:

    • Request Desktop Version: adds a cookie and reloads the page to get only desktop styles.

    • Request Mobile Version: removes the cookie and reloads to get mobile styles

    After removing these, I get a consistent number of tests completed with the message "Done, without errors."