Monday, November 2, 2009

Testing jQuery with env.js + RhinoUnit

The previous (Web application) project I was on started adopting jQuery to implement more client-side browser behaviour. We were already using RhinoUnit for testing some generic Javascript logic, but jQuery required access to the browser DOM. Hence, env.js was used to provided a DOM to RhinoUnit. This combination would be very quick due to RhinoUnit running headlessly and not relying on a browser, which was perfect for TDD. Getting these three pieces to work together properly took some work, as described below.

Versions
  • jQuery 1.3.2
  • env-js.1.0.rc7, using env.rhino.js
  • RhinoUnit 1.2.1

Replaced self
As mentioned in Adam Scott's blog, references to self in RhinoUnit needed to be replaced. So at the top of the rhinoUnitAnt.js, I added this:
var rhinoUnitSelf = self;
All references to self below this were replaced with references to rhinoUnitSelf.

Replaced print
Adam Scott's blog also mentioned the need to replace calls to print with self.log function. Specifically, reimplemented $env.log in env.rhino.js like so:

$env.log = function(msg, level){
rhinoUnitSelf.log(' '+ (level?level:'LOG') + ':\t['+ new Date()+"] {ENVJS} "+msg);
};


Implemented load
For some strange reason, env.js relies on a load function that could not be found. The solution was just to implement it using RhinoUnit's loadFile. The following was added to the top of env.rhino.js:

load = function(x) {
eval(loadFile(x));
};


Ignored global variables
jQuery and our unit tests created a lot of global variables that RhinoUnit didn't like. Instead of configuring RhinoUnit to ignore a massive list of global variables, I decided to simply remove the code that checked for this. Specifically, reimplemented ignoreGlobalVariableName in rhinoUnitAnt.js as such:

function ignoreGlobalVariableName(name) {
return true;
}


Cleanup after tests
At this stage, our unit tests could run individually, but they failed when executed together (consecutively, from Ant). This was because the global variables that were created during the first test were not cleaned up and they corrupted the environment for all subsequent tests. In order to fix this, I modified the end of the runTest(file) function in rhinoUnitAnt.js so that global variables created in one testcase (test file) were cleaned up prior to running the next testcase.

/***** ADDED to remember global variables that existed prior to each test file *****/
var origGlobalVars = {};
var varName;
for (varName in this) {
origGlobalVars[varName] = true;
}
/************************************************************************************/

eval(loadFile(file));

var testCount = 0;
var failingTests = 0;
var erroringTests = 0;

executeTestCases(test, test.setUp, test.tearDown);

if (testCount === 0) {
erroringTests += 1;
testfailed = true;
failingTestMessage(file, "No tests defined");
}

rhinoUnitSelf.log("Tests run: " + testCount + ", Failures: " + failingTests + ", Errors: " + erroringTests);
rhinoUnitSelf.log("");

/****** ADDED to remove only those global variables that have been
added by the test file, but not those that existed before. ******/
for (varName in this) {
if (origGlobalVars[varName] != true) {
delete this[varName];
}
}
/*********************************************************************/


After these changes, our jQuery unit tests ran perfectly, allowing us to do TDD for our jQuery code.