Anders Tornblad, web developer

I'm all about the web

JavaScript unit test framework, part 4

This is the last part of the first subject – my JavaScript unit test framework. The last thing I'm adding are just some extra bells and whistles:

  • Pluggable output system, using custom events

New requirements

  1. Events should be triggered on the window object at key points in time
    • When run() is called, the lbrtwUtEngineStarting event should be triggered
    • Before running one unit test, the lbrtwUtTestStarting event should be triggered
    • After running one unit test, the lbrtwUtTestEnded event should be triggered
    • After running the last unit test, the lbrtwUtEngineEnded event should be triggered
    • The lbrtwUtEngineStarting event object should contain the testCount property
    • The lbrtwUtTestStarting event object should contain the testName and testIndex properties
    • The lbrtwUtTestEnded event object should contain the testName, testIndex, testSuccess, testErrorMessage, testCount, successCount and failureCount properties
    • The lbrtwEngineEnded event object should contain the testCount, successCount, failureCount, testNames and failures properties

Tenth requirement

When testing for events being thrown to the window, it is important to clean up after oneself by removing all event listeners. These are the tests:

engine.add("Calling run() should trigger the lbrtwUtEngineStarting event once", function(testContext, the) { // Arrange var eng = new ut.Engine(); var triggered = 0; var onStarting = function(event) { window.removeEventListener("lbrtwUtEngineStarting", onStarting, false); triggered++; }; window.addEventListener("lbrtwUtEngineStarting", onStarting, false);   // Act testContext.actAndWait(100, function() { eng.run(); }).   // Assert thenAssert(function() { the(triggered).shouldBeExactly(1); }) });   engine.add("The lbrtwUtEngineStarting event object should contain testCount", function(testContext, the) { // Arrange var eng = new ut.Engine(); eng.add("a", function() {}); eng.add("b", function() {}); var testCount; var onStarting = function(event) { window.removeEventListener("lbrtwUtEngineStarting", onStarting, false); testCount = event.testCount; }; window.addEventListener("lbrtwUtEngineStarting", onStarting, false);   // Act testContext.actAndWait(100, function() { eng.run(); }).   // Assert thenAssert(function() { the(testCount).shouldBeExactly(2); }) });   engine.add("lbrtwUtTestStarting event should be triggered once per unit test", function(testContext, the) { // Arrange var eng = new ut.Engine(); var triggered = {}; var triggeredByIndex = []; eng.add("a", function() {}); eng.add("b", function() {}); var onTestStarting = function(event) { triggered[event.testName] = (triggered[event.testName] || 0) + 1; triggeredByIndex[event.testIndex] = (triggeredByIndex[event.testIndex] || 0) + 1; }; window.addEventListener("lbrtwUtTestStarting", onTestStarting, false);   // Act testContext.actAndWait(100, function() { eng.run().then(function() { window.removeEventListener("lbrtwUtTestStarting", onTestStarting, false); }); }).   // Assert thenAssert(function() { the("a").propertyOf(triggered).shouldBeExactly(1); the("b").propertyOf(triggered).shouldBeExactly(1); the(0).propertyOf(triggeredByIndex).shouldBeExactly(1); the(1).propertyOf(triggeredByIndex).shouldBeExactly(1); }) });   engine.add("lbrtwUtTestEnded event should be triggered once per unit test", function(testContext, the) { // Arrange var eng = new ut.Engine(); var results = {}; eng.add("a", function() {}); eng.add("b", function() { throw "Crash!"; }); var onTestEnded = function(event) { results[event.testName] = { success : event.testSuccess, errorMessage : event.testErrorMessage, successCount : event.successCount, failureCount : event.failureCount, testCount : event.testCount }; }; window.addEventListener("lbrtwUtTestEnded", onTestEnded, false);   // Act testContext.actAndWait(100, function() { eng.run().then(function() { window.removeEventListener("lbrtwUtTestEnded", onTestEnded, false); }); }).   // Assert thenAssert(function() { the("a").propertyOf(results).shouldBeTruthy(); the("success").propertyOf(results.a).shouldBeTrue(); the("errorMessage").propertyOf(results.a).shouldBeNull(); the("successCount").propertyOf(results.a).shouldBeExactly(1); the("failureCount").propertyOf(results.a).shouldBeExactly(0); the("testCount").propertyOf(results.a).shouldBeExactly(2);   the("b").propertyOf(results).shouldBeTruthy(); the("success").propertyOf(results.b).shouldBeFalse(); the("errorMessage").propertyOf(results.b).shouldBeExactly("Crash!"); the("successCount").propertyOf(results.b).shouldBeExactly(1); the("failureCount").propertyOf(results.b).shouldBeExactly(1); the("testCount").propertyOf(results.b).shouldBeExactly(2); }) });   engine.add("The lbrtwUtEngineEnded event object should contain testCount, failureCount, successCount, testNames and failures", function(testContext, the) { // Arrange var eng = new ut.Engine(); eng.add("a", function() {}); eng.add("b", function() {}); eng.add("c", function() { throw "Crash!"; }); var testCount; var failureCount; var successCount; var testNames; var failures;   var onEnded = function(event) { window.removeEventListener("lbrtwUtEngineEnded", onEnded, false); testCount = event.testCount; failureCount = event.failureCount; successCount = event.successCount; testNames = event.testNames; failures = event.failures; }; window.addEventListener("lbrtwUtEngineEnded", onEnded, false);   // Act testContext.actAndWait(100, function() { eng.run(); }).   // Assert thenAssert(function() { the(testCount).shouldBeExactly(3); the(failureCount).shouldBeExactly(1); the(successCount).shouldBeExactly(2); the("length").propertyOf(testNames).shouldBeExactly(3); the("length").propertyOf(failures).shouldBeExactly(1); }) });

The implementation is actually pretty easy. I write a helper function for dispatching my custom events, and then I use it at various points during the test process. Here is the helper function. Download the code below to see the complete implementation.

var triggerEvent = function(name, properties) { var event = document.createEvent("HTMLEvents"); event.initEvent(name, true, false);   for (var key in properties) { event[key] = properties[key]; }   window.dispatchEvent(event); };   // call like this: // triggerEvent("lbrtwUtTestStarting", { // testName : name, // testIndex : index // });

Making the output pretty

By listening to the four events dispatched by the framework it is possible to create any output you like. When you download the code below, you will find an example of what you can do.

Sending results to your server

By listening to the lbrtwUtEngineEnded event, I can now send the complete test results to any server using XMLHttpRequest directly or using any of the jQuery.ajax shortcuts.

$.on("lbrtwUtEngineEnded", function(event) { var oev = event.originalEvent;   $.post("http://myserver/unit-test-results", { userAgent : navigator.userAgent, testCount : oev.testCount, successCount : oev.successCount, failureCount : oev.failureCount });   // Of course, you can also use the failures and testNames properties });   myEngine.run();

The finished code can be found on github at /lbrtw/ut

JavaScript unit test framework, part 1
JavaScript unit test framework, part 2
JavaScript unit test framework, part 3
JavaScript unit test framework, part 4 (this part)

Add a comment