Assert
class on top of Rhino which was, at this time, sufficient for me to get things done.Later when I wanted regular testing during continuous integration, I looked for unit testing frameworks and found Tiest Vilee's RhinoUnit which did the job well. It felt much like JUnit, as implied by its name, and I had no problems using it. After some time I noticed Jasmine being referenced in several presentations and set out to try it. It is a behaviour-driven development framework for testing JavaScript code and does not depend on any other frameworks.
Jasmine and Rhino
Others have successfully ran Jasmine with Rhino using Envjs. (Here is a more detailed description how to do that.) Their intention was to test JavaScript used in web pages without launching a browser by using the simulated browser environment provided by Envjs. As I did not work in a browser environment this approach seemed wrong and I looked for alternatives. David Green used Jasmine on Rhino in his Rescripter, just providing missing global definitions and I worked on top of his code:
(function(global) { // timer functions from global for "jasmine.Clock" var timer = new java.util.Timer(); var counter = 1; var ids = {}; global.setTimeout = function(fn, delay) { var id = counter; counter += 1; ids[id] = new JavaAdapter(java.util.TimerTask, { run : fn }); timer.schedule(ids[id], delay); return id; }; ... })(this);
Standalone Rhino
The version of Rhino shipped with the JDK has been modified by Sun. For example the
JavaAdapter
has been removed, as written in this document by Oracle. As jasmine-rhino.js
(listed above) needs the JavaAdapter
to implement a TimerTask
, I used the standalone Rhino. To invoke it from javax.script
, there must be a ScriptEngineFactory
but the standalone Rhino does not provide any. Fortunately I found com.sun.phobos.script.javascript.RhinoScriptEngineFactory
on java.net.Jasmine Reporters
Jasmine uses reporters to report test failures in different formats. Larry Myers offers several useful Jasmine Reporters, e.g.
jasmine.console_reporter.js
or jasmine.junit_reporter.js
which creates XML files suitable for JUnit report generation. These reporters expect an Envjs console and I found myself turning back to Envjs after all. But in the end I created my own console that delegated to the Ant task logger.Console = function() { var logger = { error : function(message) { self.log(message, 0); }, warn : function(message) { self.log(message, 1); }, ... }; return logger; }; this.console = new Console();To finally run Jasmine I created a spec runner (
AntSpecRunner.js
) derived from Jasmine's original SpecRunner.html
which started the tests and waited for all of them to finish.this.execJasmine = function() { var jasmineEnv, apiReporter; jasmineEnv = jasmine.getEnv(); jasmineEnv.updateInterval = 100; apiReporter = new jasmine.JsApiReporter(); jasmineEnv.addReporter(apiReporter); jasmineEnv.execute(); while (!apiReporter.finished) { java.lang.Thread.sleep(100); } }; console.log('Jasmine loaded.');
Rhino and Ant
For continuous integration I added Ant support. Now Ant is not the newest nor hottest technology available but I know my way around. Inspired by RhinoUnit I load Jasmine code to execute in Rhino using Ant's
Scriptdef
task. The script implementation iterates all files of all provided FileSet
s and calls runTest
with each file. Note that self
points to Ant's ScriptDefBase
task implementation by default.var testfailed = false; // helper methods and Ant attributes omitted var filesets = elements.get("fileset"); for (var j = 0; j < filesets.size(); j += 1) { var fileset = filesets.get(j); var directoryScanner = fileset.getDirectoryScanner(project); var srcFiles = directoryScanner.getIncludedFiles(); forEachElementOf(srcFiles, function(srcFile) { var jsfile = new java.io.File(fileset.getDir(project), srcFile); runTest(jsfile); if (testfailed && haltOnFirstFailure) { self.fail("Jasmine failed."); } }); } if (testfailed) { self.fail("Jasmine failed."); }Each source file, supposed to be a Jasmine spec, is loaded and executed. I created a special Jasmine reporter,
jasmine.CountsReporter
, to get the number of failed tests back from Jasmine to record failures.function runTest(file) { var failingTests; // helper methods omitted try { Load.file(file); } catch (e) { erroringTestMessage(file, e); testfailed = true; } failingTests = this.jasmine.getEnv().countsReporter.failingTestNames(); if (failingTests.length > 0) { forEachElementOf(failingTests, function(testName) { failingTestMessage(testName); }); testfailed = true; } }(See the complete integration working here.)
Although I was reusing a lot from
rhinoUnitAnt.js
and copying from David and others, to integrate Jasmine, Rhino and Ant took me much longer than I had expected. While this might be a general statement about any coding task, I am wondering if I should have went with one of the existing solutions...
2 comments:
Thanks for this great post, it was really useful to me. I took this stuff and got it working under Maven too. Blogged about it here.
Thanks again, Code Cop!
Thank you for letting me know. I am happy this is useful for someone at least ;-)
Post a Comment