Unit Testing JavaScript


Unit Testing JavaScript

In my daily life as a software engineer (be it .Net, Dynamics CRM or SharePoint) everything I do needs to be tested.  Manual testing is not only tedious but becomes error prone – specially on your 40th iteration of a given test. Enter Automated Unit Testing.  By now we’ve all seen test frameworks built into our IDE (I’m going to use Visual Studio Code).  They are great for compiled code like C#, VB.net but what happens when your changes are to JavaScript?  You reach for a Unit Test framework – in the same way you would for C#.  I’ve had reliable results with https://qunitjs.com/.  If it’s good enough for the good people at jQuery its good enough for my meagre needs.

QUnit is a powerful, easy-to-use JavaScript unit testing framework. It's used by the jQuery, jQuery UI and jQuery Mobile projects and is capable of testing any generic JavaScript code, including itself!

The Solution

I started with setting up a solution in VS Code with the following structure
·         VSCode
o   Sources
§  DateValidation.js
o   Tests
§  Blog.Tests.DateValidation.js
§  Blog.Tests.UnitTests.html


The use-case

I needed to validate some dates.  In this example, I need to be able to check whether one date is within N number of hours from now, and secondly, I need to be able to check one date is less than a second date.

The method stubs

I’m going to use a namespaced JavaScript file for two reasons – it keeps the global variable list tidier, and to my object driven brain it’s a little cleaner visually.
First, I create an object called DateFunctions which contains two functions:
·         MaxHrsInFuture, and
·         LessThan
Our code should look something like this
var DateFunctions = {
    MaxHrsInFuture: function (now, newTime, maxHrs) {
        //compare the dates in here
    },
    LessThan: function (date1, date2) {
        //return true if date1 is less than date2
        //else return false
    }
}

The unit tests

As I’m going to automate these tests, I can afford to handle a larger number of unit tests.  First though I need to download the latest QUnit package to my test solution, and build a simple HTML page that will allow me to run the tests (the test harness)

Writing the unit tests

Test Harness

Using QUnit is simple, create a basic HTML file, include links to the CSS and its JS file, then include a link to the files which contain the functions you want to test and a test collection.
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>QUnit - JavaScript Unit Testing</title>
  <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.1.1.css">
</head>
<body>
  <div id="qunit"></div>
  <div id="qunit-fixture"></div>
  <script src="https://code.jquery.com/qunit/qunit-2.1.1.js"></script>
  <script src="../Sources/Blog.DateValidation.js"></script>
  <script src="Blog.Tests.DateValidation.js"></script>
</body>
</html>

Writing the tests

Using the principles of Test Driven Development, I build the tests before I flesh out the methods themselves.   This way as I execute the tests I can be sure my code is progressing the right way. 
The functions I want to test are quite simple but I’ll still create several test cases- including some edge cases, and some failing cases – this way I know how the code will behave when things don’t go the way I want them to.
QUnit.module( "Date Functions - Validation" );
QUnit.test("Validate Date", function( assert ) {
  var now = "2008/01/28 22:25:00";
  assert.equal(DateFunctions.MaxHrsInFuture(now, "2008/01/28 10:24:30", 24), true, "< 24hrs difference = return true");
  assert.equal(DateFunctions.MaxHrsInFuture(now, "2008/01/29 23:23:30", 24), false, "> 24hrs difference = return false");
  assert.equal(DateFunctions.MaxHrsInFuture(now, "2008/01/28 21:23:30", 24), true, "< 24hrs difference = return true");
  assert.equal(DateFunctions.MaxHrsInFuture(now, "2009/01/26 22:23:30", 24), false, "> 24hrs difference = return false");
  assert.equal(DateFunctions.MaxHrsInFuture(now, "2008/01/30 22:23:30", 48), true, "< 24hrs difference = return true");
  assert.equal(DateFunctions.MaxHrsInFuture(now, "2008/01/30 23:23:30", 48), false, "> 24hrs difference = return false");
  assert.equal(DateFunctions.MaxHrsInFuture(now, "2008/01/27 22:23:30", 24), true, "Date in past = return true");
  assert.equal(DateFunctions.MaxHrsInFuture(now, "2008/01/26 22:23:30", 24), true, "Date in past = return true");
 });

QUnit.module( "Date Functions - Comparison");
QUnit.test("Validate Date", function( assert ) {
  var now = "2008/01/28 22:25:00";
  assert.equal(DateFunctions.LessThan(now, "2008/01/28 10:24:30"), false, "date1 < date 2 = result false");
  assert.equal(DateFunctions.LessThan(now, "2008/01/29 23:23:30"), true, "date1 < date 2 = result true");

 });

Note that all the dates are relative to a date “now” which is set to a specific point in time.
If I open the Blogs.Test.UnitTests.html file in my browser now, I get a big red section telling me the tests failed – but I’m expecting that as I’ve not actually written the body of my methods.


Creating the testable functions

Now the real work begins, I open the file Sources/Blog.DateValidation.js and enter the body of the methods, and save it.
var DateFunctions = {
    MaxHrsInFuture: function(now, newTime, timePeriod) {
        var decTimePeriod = timePeriod.toFixed(4);
        var newDate = new Date(newTime || "");
        var hrsDiff = (newDate - (new Date(now))) / 36e5;

        if (isNaN(hrsDiff) || hrsDiff > decTimePeriod) {
            return false;
        } else {
            return true;
        }
    },
    LessThan: function(date1, date2) {

        var firstDate = new Date(date1);
        var secondDate = new Date(date2);
        if (firstDate < secondDate) {
            return true;
        }
        return false;
    }
}


Now when I refresh the Blog.Tests.UnitTest.html file I can see my tests have passed.



There are several key factors in being able to unit test JavaScript that are used in this post
  •  The JavaScript is “namespaced” into its own space, this keeps the methods separate from any others which may be imported from other libraries.
  • I’ve kept my code separate from anything working on the Document Object Model (DOM) – separate tests could be written for the methods retrieving values from the DOM; these would be classed as integration tests though.

Let me know if you have any questions on this or other blog posts.


No comments:

Note: only a member of this blog may post a comment.

Powered by Blogger.