Skip to content

cjohansen.no

A test driven example

With my two introductory posts on JavaScript testing under the belt, it's time to do some actual testing. In todays post I walk you through a pretty simple example in test driven development with JavaScript, coding a trim() function. The end result is presented with Qunit, jsunittest.js and YUI Test.

trim()

A fairly simple (and oft-used) example is developing a string trim() function. To keep things simple, we'll build a standalone function that accepts a string argument. For those of you who prefer the more object oriented solution, I present a "string".trim(); towards the end of this post.

The spec

trim() accepts a string input and removes all leading and trailing white-space. Let's start with a test that illustrates the basic functionality. Tests are written using jstestunit.js - QUnit and YUI Test solutions are available at the end.

new Test.Unit.Runner({
    testTrimTrailingSpace: function() { with(this) {
        assertEqual("String", trim("String "), "Trailing space should be removed");
    }},

    testTrimLeadingSpace: function() { with(this) {
        assertEqual("String", trim(" String"), "Leading space should be removed");
    }},

    testNoTrimInnerSpace: function() { with(this) {
        var sentence = "A complete sentence with     lots of whitespace.";
        assertEqual(sentence, trim(sentence), "Inner space should not be removed");
    }}
}, { testLog: "testlog" });

Running the test fails (since we have yet to implement the feature): Test #1

Note that it's equally important to check that a function doesn't accidentally do something it shouldn't do, as to check that it actually does what it should.

Initial implementation

Let's make a first pass at the solution, and provide the simplest solution that solves the problem at hand:

function trim(str) {
    return str.replace(/^\s|\s$/, '');
}

The solution solves the problem by way of a regular expression.t eksempel på.

Running our unit test again yields promising results: Test #2.

Bugs!

Oh noes, someone tripped on a bug in trim()! It seems that our function only clears out a single leading and trailing space. First, let's try to reproduce the bug in a controlled environment - our test case:

testTrimTrailingSpace: function() { with(this) {
    assertEqual("String", trim("String "), "Trailing space should be removed");
    assertEqual("String", trim("String   "), "All trailing spaces should be removed");
}}

Run: Test #3. Sure enough, the test fails. That's a good thing - it means we've isolated the bug, and now have a way of verifying the solution. Let's get rid of it:

function trim(str) {
    return str.replace(/^\s+|\s+$/, '');
}

Adding a plus means the regex will remove one or more white-space characters in both ends. Run the test over, and voila! It works! Can you sense that awesome feeling of trust in your own code?

Another bug

Our celebration only lasts so long before another bug surfaces. trim() seems unable to clear white-space at both ends of the string at the same time:

testTrimTrailingAndLeadingSpace: function() { with(this) {
    assertEqual("String", trim(" String "),
                "Both leading and trailing space should be removed");
    assertEqual("String", trim("   String   "),
                "All leading and trailing spaces should be removed");
}}

Test #5.

Ok, so we need to set the global flag on our regex:

function trim(str) {
    return str.replace(/^\s+|\s+$/g, '');
}

Now try it again: Test #6.

For the final touch, let's add a test that verifyes that different kinds of white-space characters are removed, not only spaces:

testTrimAnyWhiteSpace: function() { with(this) {
    assertEqual("String", trim(" \n\t String"),
                "Any kind of whitespace should be trimmed");
    assertEqual("String", trim("   String\n"),
                "Trailing newlines should be trimmed");
}}

Test #7 reveals that this already works.

Browsers

So, we've got a new feature, and we used a single browser developing it. Having repeatable unit tests around means we can now verify the cross-browserness of our code simply be running the tests in all the browsers we can come across. If another bug surfaces, we'll just isolate it with a test and solve it. Finally the test case will run cleanly in all the target browsers and we're done.

By utilizing test driven development for JavaScript we can eliminate huge amounts of monkey testing time. Time spent hitting the refresh button in IE6, or dropping in dozens of alert() or console.log() could instead be cleverly invested in writing unit tests. These unit tests can even be run on new browsers when they emerge to quickly identify potential problems with upcoming updates.

All versions

As promised, you can also check out this version which builds the function onto the String.prototype instead, allowing for "string ".trim(); instead of trim("string ");.

In my next post I'll take you through a little bit more complex example, giving you some more tests to study.

Possibly related