Testing Your JavaScript

I've noticed that there has been a lot of talk lately about TDD, or Test Driven Development. Personally, I see it useful in certain areas and not so much in others. But, one thing that I've found TDD really great for is Javascript libraries, or really any dynamic language for that matter.

Dynamic languages have a chance of being altered by anything at anytime. It makes them particularly difficult to debug. Have you ever tried this before?

window.alert = document.body;
window.alert.innerHTML = "Dude, what?";

Yep, believe it or not, that code actually runs and works as advertised!

So you're working on a large Javascript library, how can you test? There is plenty of libraries out there that you can download and use, but to show just how easy it is to start testing your code, we are going to write our own.

What Makes A Test?

It's a good question! Basically you take some code from your library, check the results of a method or series of methods and then return back if the test succeeded or not.

That's an overly simplistic view of a test - but it's sufficient for this example. Here is some code I wrote for my jLinq tests.

setTimeout(function() {
(function(tests) {
    var self = this;
    self.index = 0;
    self.errors = [];
    self.target = document.getElementById("results");

    this.assert = function(ok, msg) {
        if (ok) { return; }
        self.errors.push(msg);
    };

    var test = function() {

        //reset
        self.errors = [];

        //try the method
        try {
      //document.body.innerHTML += "<hr/>" + tests[self.index].name;
            this.current = tests[self.index].method;
            this.current();
        }
        catch (e) {
            self.errors.push("Exception: " + e);
        }

        //if not okay, display the errors
        var result = ["<div class='result result-"];
        if (self.errors.length > 0) {
            result.push("error' >");
            result.push("<div class='result-title'>#" + 
        (self.index + 1) + ": " + tests[self.index].name  
        + "</div>");
            result.push("<div class='result-errors' >" + 
        self.errors.join("<br />") + 
        "</div>");
        }
        else {
            result.push("success' >");
            result.push("<div class='result-title'>#" + 
        (self.index + 1) + ": " + tests[self.index].name  
        + "</div>");
        }
        result.push("</div>");
        self.target.innerHTML += result.join("");

        //set the next test
        self.index++;
        if (self.index >= tests.length) { return; }
        setTimeout(test, 1);

    };

    //start the tests
    test();

})([
    //Actual Tests go here
    //more explained in a moment
    //.......

])}, 250);

The code basically calls the same test routine each time, but increments our test index each time. This way all of our tests are completed, but are also done with a slight pause between each to keep our browser from locking up.

Adding A Test

So how do we actually create a test. What do we need to have for this to work? Well since we're passing in array of tests our code will look something like this...


//Tests
{name:"Function charAt correctly finds values at indexes",
method:function() {
    this.assert("abcd".charAt(0) == "a", 
  "Did not locate correct letter checking start of string");
    this.assert("abcd".charAt(3) == "d", 
  "Did not locate correct letter checking end of string");
}},

{name:"Function calculateRate correctly returns percentage based on values",
method:function() {
    this.assert(calculateRate(11000) == 0.5, 
  "Did not calculate correct rate from values greater than 10,000");
    this.assert(calculateRate(5100) == 0.3, 
  "Did not calculate correct rate from values greater than 5,000");
    this.assert(calculateRate(1100) == 0.1, 
  "Did not calculate correct rate from values greater than 1,000");
    this.assert(calculateRate(100) == 0, 
  "Did not calculate correct rate from values less than 1,000");
}},

{name:"Runs a function (this test just fails for this blog example)",
method:function() {
    this.assert(someFakeFunction(), "We won't see this message");
}}

The tests above are some examples of tests you could write. I'm not an expert on this stuff by any stretch of the imagination, but these tests seem solid enough to tell us if one of our functions fail.

The last example calls a function that doesn't exist, which in my code, passes the exception along as the error message.

So Why Does This Help

Remember the code sample at the start of the post? Where I just up and changed what window.alert does? Let's say we had our tests and then somewhere in our code we did this...

String.prototype.charAt = function() { return "a"; }

It's perfectly legal and it completely breaks what we expect our results to be.

Now, jLinq is 1655 lines of code right now, clearly not a behemoth of a library like jQuery, but large enough that I don't remember what I wrote everywhere, but now that I have tests it doesn't matter as much. I'm able to write some changes to the code, run the test and be certain that the library is still in tact. In fact, I wish I would have done it sooner.

What's Next?

Don't consider this to be all there is to TDD, but instead a very basic explanation of the concept of TDD. I would recommend to you that if you're working on large project, especially dynamic languages, make sure you write tests for all of your functions. They will save you in the long run.

June 3, 2009

Testing Your JavaScript

Post titled "Testing Your JavaScript"