JavaScript for C# developers: writing a library (part 4)

Last time, I’d completed the cookie code. Well, “completed” in the sense of written it, but I now have to think about testing it. The writers of jQuery use a testing library called QUnit for their testing, so that’s what I’ll do too.

testphoto © 2006 David Bleasdale | more info (via: Wylio)It’s constructed pretty much in the same manner as every unit testing library: there are tests, ways to group tests together, ways to compare actual to expected values, and so on. The set up though is a little bizarre since it’s entirely done through Unobtrusive JavaScript, so we’ll look at that first.

Unobtrusive JavaScript is the methodology that says your HTML markup should contain no JavaScript at all. Sure, there has to be <script> elements, but those elements only reference external JavaScript files that should be loaded. In particular, there should be no JavaScript snippets embedded in the HTML, such as for event handlers (like mouse clicks) and the like. Certainly there should be no embedded JavaScript code chunks or islands. In essence, complete separation of the presentation code (that is, the HTML and CSS) from the code that operates on the presentation.

Why am I stressing this? Because here’s the HTML for the test presentation layer for QUnit and you’ll see there’s no JavaScript code at all. Create a new HTML file in your project (I’ve called this one DateTests.html since I’ll be demonstrating how to use QUnit on the date routines first) and insert the following markup:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <link href="qunit.css" rel="stylesheet" type="text/css" />
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js" type="text/javascript"></script>
    <script src="Scripts/jmbBase.js" type="text/javascript"></script>
    <script src="Scripts/jmbDate.js" type="text/javascript"></script>
    <script src="Scripts/jmbDateTests.js" type="text/javascript"></script>
    <script src="Scripts/qunit.js" type="text/javascript"></script>
</head>
<body>
  <h1 id="qunit-header">jmbDate tests</h1>
  <h2 id="qunit-banner"></h2>
  <div id="qunit-testrunner-toolbar"></div>
  <h2 id="qunit-userAgent"></h2>
  <ol id="qunit-tests"></ol>
</body>
</html>

As you can see there’s not a lot there. After the usual HTML preamble, in the <head> element we first have a <link> tag that loads the QUnit CSS file. (QUnit consists of two parts: the JavaScript file, which we’ll get to in a moment, and the CSS file. The CSS file will style the results page generated by the unit tests.) Then we load jQuery, my JavaScript library files (ignoring the cookie one here), then a new JavaScript file called jmbDateTests.js (it’ll contain the unit test code I’m going to be writing), and finally the QUnit.js file that contains the unit testing library code. (You can download the QUnit CSS file here and the JS file here.)

Then comes the body of the page. To say there ain’t much there is an understatement. Essentially all there is is some specially ID’ed markup. The QUnit site has this to say about it all:

The #qunit-header element should contain the name of the testsuite, and won't be modified by QUnit. The #qunit-banner element will set to show up as red if a test failed, green if all tests passed. The #qunit-userAgent elements is set to display the navigator.userAgent property. The #qunit-tests element will be used as a container for the test results.

So, minimal markup and the magic code in QUnit will be populating the various sections without us having to worry about it. Just copy the HTML shown, change the JavaScript filenames that are loaded to yours and you have the test bed for your own libraries.

In order to execute the unit tests I’m about to write, we structure the jmbDateTests.js file to contain a function that will be executed once the document is fully loaded. We do this in the usual manner when using jQuery:

jQuery(function () {

  // test code goes here

});

That is, we pass an anonymous function directly to the jQuery function (I could have used $ of course), add we’ll put our test code inside the anonymous function where the comment says.

First up, we declare a new module to QUnit (it appears in the results page, but it is optional, especially when we’re just testing one module as we are here):

  module("jmbDate");

And then we can write our first test case. I’m going to test the isDate function first. This should, in effect, return false to everything passed in except if the parameter is a Date object.

  test("isDate", function () {
    expect(14);
    var $j = jQuery.jmbLibrary;
    var $jd = $j.date;
    var undefined;

    ok(!$jd.isDate(), "no parameter");
    ok(!$jd.isDate(undefined), "undefined parameter");
    ok(!$jd.isDate(null), "null parameter");
    ok(!$jd.isDate(""), "empty string parameter");
    ok(!$jd.isDate(0), "zero parameter");
    ok(!$jd.isDate(NaN), "NaN parameter");
    ok(!$jd.isDate(false), "false parameter");
    ok(!$jd.isDate({}), "object parameter");
    ok(!$jd.isDate([]), "array parameter");
    ok(!$jd.isDate(Date), "Date constructor parameter");

    ok(!$jd.isDate(true), "true parameter");
    ok(!$jd.isDate(42), "number parameter");
    ok(!$jd.isDate("01-Jan-2010"), "string parameter");

    ok($jd.isDate(new Date()), "current date parameter");
  });

The test function takes two parameters, a string and a function that will execute the tests. In this function, we first declare how many individual tests we’re expecting to run as part of this test (14), then we  do the usual stuff to get some local variables to make things easier to write. After that’s set up, all the individual tests use the ok function. This takes two parameters: some expression that should evaluate to true and a string describing the test. As you can see, I first test no parameter being passed at all, an undefined value, a null value, etc, all of which should return false from the isDate function. Finally, there’s a test to check that the function returns true for a real date.

(Aside: note that I declare a variable called undefined whose value is actually undefined. Bizarrely, JavaScript does stipulate that the undefined value is constant, like null, and but that it can be redefined, unlike null. So you could actually set it to some real value like “hello” or 42 and really screw up other people’s code.)

Here’s the test for the now function. Try as I might the only way I could think of to test this involved a slight race condition if the test was actually run around midnight local time. The reason is my expected value might fall on an earlier date (a smidgeon before midnight) than the actual value (a smidgeon after). Too close to call, probably, besides which I doubt I’ll ever run this at midnight. Rerunning the test would immediately pass as well.

  test("now", function () {
    expect(3);
    var $j = jQuery.jmbLibrary;
    var $jd = $j.date;

    // very slight race condition if test run at midnight
    var today = new Date();
    var now = $jd.now();

    equal(now.getDate(), today.getDate(), "check the day of the month");
    equal(now.getMonth(), today.getMonth(), "check the month of the year");
    equal(now.getFullYear(), today.getFullYear(), "check the year");
  });

Here I make use of the equal test function to check that the actual and expected values are equal. It requires three parameters: the actual value, the expected value, and a string describing the test being made.

Finally here’s the code that tests the addDays function.

  test("addDays", function () {
    expect(12);
    var $j = jQuery.jmbLibrary;
    var $jd = $j.date;

    var today = new Date(2010, 11, 21);
    var lastweek = $jd.addDays(today, -7);
    var nextweek = $jd.addDays(today, 7);
    var lastmonth = $jd.addDays(today, -30);
    var nextmonth = $jd.addDays(today, 30);

    equal(lastweek.getDate(), 14, "-7: check the day of the month");
    equal(lastweek.getMonth(), 11, "-7: check the month of the year");
    equal(lastweek.getFullYear(), 2010, "-7: check the year");

    equal(nextweek.getDate(), 28, "+7: check the day of the month");
    equal(nextweek.getMonth(), 11, "+7: check the month of the year");
    equal(nextweek.getFullYear(), 2010, "+7: check the year");

    equal(lastmonth.getDate(), 21, "-30: check the day of the month");
    equal(lastmonth.getMonth(), 10, "-30: check the month of the year");
    equal(lastmonth.getFullYear(), 2010, "-30: check the year");

    equal(nextmonth.getDate(), 20, "+30: check the day of the month");
    equal(nextmonth.getMonth(), 0, "+30: check the month of the year");
    equal(nextmonth.getFullYear(), 2011, "+30: check the year");
  });

As you can see I decided to check +/- 7 days as well as +/- 30 days starting from a fixed given date.

If I now load the DateTests.html file in Firefox I get this (click to enlarge):

Date tests results

Or you can see the actual tests being run here in your own browser.

Just to show what happens if a test fails, I’ll fudge one of the tests to break:

Date test fails

Next time, we’ll look at testing the cookie code.

Album cover for IncantationsNow playing:
Oldfield, Mike - Part Two
(from Incantations)


Loading similar posts...   Loading links to posts on similar topics...

No Responses

Feel free to add a comment...

Leave a response

Note: some MarkDown is allowed, but HTML is not. Expand to show what's available.

  •  Emphasize with italics: surround word with underscores _emphasis_
  •  Emphasize strongly: surround word with double-asterisks **strong**
  •  Link: surround text with square brackets, url with parentheses [text](url)
  •  Inline code: surround text with backticks `IEnumerable`
  •  Unordered list: start each line with an asterisk, space * an item
  •  Ordered list: start each line with a digit, period, space 1. an item
  •  Insert code block: start each line with four spaces
  •  Insert blockquote: start each line with right-angle-bracket, space > Now is the time...
Preview of response