Testing JavaScript with JSpec

I was recently involved in some JavaScript work, for which we used JSpec to run automated unit tests within a continuous build. We ran into a few problems along the way, particularly around integrating JSpec into the build, so I wanted to walk through what we did and provide a working example that could hopefully be reused for future projects and save some of the pain.

Note: it seems that JSpec is no longer being developed and they are pointing us towards Jasmine. However, JSpec still works well as a simple tool. The tests can easily be integrated into a continuous build and don’t require a browser to be installed as they are using the Rhino engine.

Step 1: Set up the project and example test

Download the jspec files

I’m going to run JSpec using Rhino instead of a browser, which requires js.jar (included in the support directory downloaded with jspec) and env.rhino.1.2.js, which can be downloaded from Envjs.

To run jspec itself in this example, I only need the following files (from the lib directory in the jspec download):

  • jspec.js
  • jspec.jquery.js
  • jspec.xhr.js
  • jquery.js (inside spec/support directory)

For the purpose of the example, I’ve created a basic JS file that gets passed a part of a DOM and sets the HTML to be “Hello World”. The test checks that the HTML is as expected.

JSpec Test

describe('Hello', function() {

    it("writes Hello World into the dom it is passed", function() {

        var dom = $("<div></div>");

        var hello = new Hello(dom);

        dom.html().should.be("Hello World");

    });

});

Hello Class to pass the test

function Hello(dom) {
    var self = this;
    self.dom = dom;

    function setUp() {
        dom.html("Hello World");
    }

    setUp();
}

More details of the format for JSpec tests.

2. Create a script to run the tests

I’m going to create one javascript file to load and run all of the tests, which will do the following:

  1. Load the files needed to run jspec
  2. Load the source javascript files
  3. Run the tests

Script to run tests: v1

load('test/lib/env.rhino.1.2.js',
        'test/lib/jspec/jspec.js',
        'test/lib/jspec/jspec.xhr.js',
        'test/lib/jquery.js',
        'test/lib/jspec/jspec.jquery.js',
        'test/lib/formatters.js');

load('src/hello.js');

JSpec.exec("test/tests/hello.spec.js");

JSpec
        .run({reporter: JSpec.reporters.Terminal })
        .report()

I can run this from within terminal with the java –jar command1:

Java –jar lib/jar.js –opt -1 test/lib/run.js

3. Adjust the script to automatically run all tests

This script works fine if there’s only one javascript file and one test file but that’s not realistic. I can create a simple fix to:

  • Load all js files from the source directory
  • Load all js files from the tests directory

The code to load the files requires the Java class java.io.File to be imported.

Loadfiles.js

importClass(java.io.File);

function isJavaScriptFile(fileName) {
    fileExtension = fileName.substring(fileName.lastIndexOf('.'));
    return (fileExtension == ".js");
}

function getFiles(directoryPath) {
    var dir = new File(directoryPath);
    return dir.listFiles();
}

Updated Test Runner Script – v2

load('test/lib/env.rhino.1.2.js',
        'test/lib/jspec/jspec.js',
        'test/lib/jspec/jspec.xhr.js',
        'test/lib/jquery.js',
        'test/lib/jspec/jspec.jquery.js',
        'test/lib/formatters.js',
        'test/lib/loadFiles.js');

var srcFiles = getFiles("src");
jQuery.each(srcFiles, function(index, model) {
    load(model);
});

function runTestSuite(testSuite) {
    JSpec.exec(testSuite);

};

var tests = getFiles("test/tests");
jQuery.each(tests, function(index, test) {
    if (isJavaScriptFile(test.getName())) {
        runTestSuite(test);
    }
});

JSpec
        .run({reporter: JSpec.reporters.Terminal })
        .report()

4. Make the source and tests folders configurable

If you want to be able to configure the location from outside of the script itself, this is how you can do it. This means that the locations can be passed in from the build script (as in the example code), or through your project configuration.

This is really straightforward, we can just use the arguments[] array:

var testDirectoryPath = arguments[0];
var srcDirectoryPath = arguments[1];

When running from terminal, the command is now:

Java –jar lib/jar.js –opt -1 test/lib/run.js test/tests test/src

5. Create an Ant build file

The build script uses Ant to execute the tests. The task to execute the tests is a java task, which runs the rhino js.jar. Each of the above arguments is passed as a separate value. Setting failonerror=”true” ensures that if the tests fail, the build fails.

<?xml version="1.0"?>
<project basedir="." name="JSpecRunner" default="run">

    <target name="run">
        <sequential>
            <java jar="lib/js.jar" failonerror="true" fork="true">
                <arg value="-opt"/>
                <arg value="-1"/>
                <arg value="test/lib/run.js"/>
                <arg value="test/lib/jspec/"/>
                <arg value="src"/>
            </java>
        </sequential>
    </target>

</project>

6. Lastly: format the output into XML

The script so far just outputs the results to the terminal, but they can also be formatted and written to an XML file so that the output is stored. This may be useful if you are running the build script from within a continuous build server like Go or CruiseControl.

For the XML formatting, I can use an open source JSpec module. In the second line of code, ‘formatters’ should be changed to read ‘reporters’.

The formatting code needs a couple of tweaks to write a file, since the last two lines of code currently print the output:

print('<?xml version="1.0" encoding="utf-8"?>n');
print(obj2xml(out));

To write a file, firstly I need to specify the file name – I can do this in the arguments passed to the script:

var resultsFile = arguments[2];

Secondly, I can create a new file using Java functions and write the XML created by the formatting script to it:

var results = new File(resultsFile);
var file_writer = new FileWriter(results);
file_writer.write("<?xml version="1.0" encoding="utf-8"?>n");
file_writer.write(obj2xml(out));
file_writer.close();

Note: This also requires the Java.IO.File and Java.IO.FileWriter classes to be imported.

The complete script outputs the following to the terminal:

Output from terminal after running jspec tests

The XML file contains the following:

<?xml version="1.0" encoding="utf-8"?>
<testsuites >
	<testsuite name="Hello" tests="1" assertions="1" failures="0" >
		<testcase name="writes Hello World into the dom it 
			is passed" assertions="1" />
		</testsuite>
</testsuites>

Complete!

The entire code can be downloaded from Github:
https://github.com/jocranford/jspecbuild

Footnotes

1. Further links for more explanation of the arguments passed to Rhino, and specifically the optimisation options.