Really Simple Testing for JavaScript

There are plenty of options to test JavaScript code. My goal here is not especially to add to this long list but I needed something for my samples that was brain dead simple to understand and that I could redistribute without any concerns about licensing (this is licensed under the very liberal MS-PL). I just think it’s good practice to distribute tests with sample code because it promotes TDD and helps to understand the intent of the code.

So I built the simplest test framework I could, both in terms of its code and ease of use. The result is very small and at 32 lines of code, it’s small enough that I can post it in its entirety here:

// Really Simple Testing for JavaScript
// (c) 2008 Bertrand Le Roy, licensed under MS-PL
function test(tests, consoleElement) {
    var results = {}, failed = 0,
        console = typeof(consoleElement) === "string" ?
            document.getElementById(consoleElement) :
            consoleElement || document.body;
    function write(message, color) {
        if (!console.appendChild) return;
        var div = document.createElement("div");
        div.style.color = color;
        div.innerHTML = message;
        console.appendChild(div);
    }
    for (var testName in tests) {
        try {
            tests[testName]();
            write(testName + " passed", "green");
            results[testName] = null;
        }
        catch (ex) {
            write(testName + " failed with " + ex.message, "red");
            results[testName] = ex;
            failed++;
        }
    }
    if (failed) write(failed + " failed." , "red");
    return results;
}
test.assert = function(condition, failMessage) {
    if (!condition) throw new Error(failMessage || "Assertion failed.");
}

Using it is just as simple. Just create an empty HTML page, include the script file and add a script tag right after the body tag. This is where you’ll write your tests. The test suite consists of a single call to the test function, where the argument is a plain object that contains a set of names and test case functions. Each test case may do one or several calls to test.assert. Assert just throws an exception if the condition is false. Any exception is interpreted as the test failing, except if that exception is expected and is being caught by the test code. Here is the test suite for testing the framework itself:

<!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>Testing Really Simple Testing for JavaScript</title> <script type="text/javascript" src="Script/RST.js"></script> </head> <body></body> <script type="text/javascript"> test({ simpleAssert: function() { test.assert(1 + 1 === 2, "1+1 is supposed to be 2."); }, failAssert: function() { try { test.assert(1 + 1 === 3, "FAIL"); } catch (ex) { test.assert(ex.message === "FAIL", "Failed to fail."); } }, testResults: function() { var testResults = test({ success: function() { }, failure: function() { throw new Error("failure"); } }, {}); test.assert(testResults.success === null,
"Success should give a null entry in the results."); test.assert(testResults.failure.message === "failure",
"Failure should store the exception in results."); } }); </script> </html>

The test function takes an optional second parameter that is the element to use as an output console (use an empty object if you don’t want any output, like I did in the testResults case or don’t specify it at all to use body). It returns an object that has the same structure as the test case parameter. Each case name points to null if the test passed, and to the exception object if it failed. This is useful when you want to use the test results and not just see them on the page. This is the case in testResults and also if you want to automate the tests and send a report somewhere.

By default the test framework will just display results in the body tag, in green for passing tests, in red with the exception message for failing ones, and if anything failed, it will display the number of failing tests:

simpleAssert passed
failAssert passed
testResults failed with Failed on purpose.
1 failed.

Here is a little FAQ to answer some of the questions you may still have:

  1. Why no assertEqual?
    I only included the bare minimum, an assert function. It’s fine for my usage. assertEqual would give a better automatic error message and if you need it, if would be very easy to write. Oh well, if you need it, here it is:
  2. test.assertEqual = function(expected, actual, failMessage) {
        if (expected !== actual) throw new Error(failMessage ||
    "Expected " + expected + ", got " + actual + "."); }
  3. What? No setup and teardown?
    No, you’ll have to call any setup and teardown functions you may have manually. Or modify the framework and add that capability. Shouldn’t add much more than four lines of code.
  4. How do I do expected exceptions?
    Just try/catch and don’t forget to assert something about the exception from the catch block. The failAssert case above gives an example of that.
  5. How do I find out what went wrong in my failing test?
    Attach a debugger, set a breakpoint in the test case, and step through the code. Know your F10, F11 and SHIFT+F11.
  6. Do I have to run all tests in the suite every time?
    Yep. Build smaller test suites or break one big suite into several small ones if that’s a problem.
  7. Does this work in all browsers?
    Yes. All browsers that support JavaScript, createElement, innerHTML and appendChild. That should be all the browsers you care about, right?
  8. How do I automate test runs?
    Pretty much all recent browsers support being launched from the command line with the url to navigate as the argument. Your page can then submit a form with serialized results as a field value to a server page that compares the results with a baseline. It’s just a matter of writing a bunch of batch files and a server page to receive the results and log them. I didn’t write that part because that wasn’t part of my goals but if you do, please tell me and I’ll post about it.
  9. How do I do asynchronous tests?
    One way you could do this is by doing your call to the test function from your asynchronous callback.
  10. How do I do deep object and array comparisons?
    Manually. What’s a little loop? Write a helper if you want, that shouldn’t be too hard.
  11. Any mocking facilities?
    No, but mocking is considerably easier in dynamic languages and JavaScript is no exception.
  12. Why put the test code after body?
    Because the framework is using it as its console so it must be ready when the script runs. You don’t have to if you want to use another element than body as the console, or if you don’t use a console, or if you run the tests on body load or on some framework’s document ready event.
  13. Is this limited to Microsoft Ajax?
    No, this doesn’t even use it. It’s plain JavaScript, no dependencies, and should work with any framework and any browser.

I hope this helps.

Download the testing framework and its test suite here:
http://weblogs.asp.net/blogs/bleroy/Samples/ReallySimpleTesting.zip

3 Comments

  • great tutorial.. I've been loking for similar one around the web and finally succeeded here :) *as always*

    Thanks

  • its really nice tutorial, thanks for it.

  • ?? ok...bare f*ing minimum so +100 Kudos points

    -500 Kudos points for being an arrogant XXX .... This is TESTING we are talking about...testers, generally are not *propeller heads* like us JS Ninjas.... fair suck of the sav - this library/script is really for the coder and not a real contender in the JS Testing space.


    All the same, well done.
    Tiggr
    MrTiggr @t g00gle mail com

Comments have been disabled for this content.