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.