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.

Encouraging Diversity

Encouraging diversity within the IT industry and the software teams in which I work is a cause that has become increasingly important to me over the last year. Women are an under-represented group within IT, and for me this is a key area I would love to see improve.

It’s not uncommon to interpret ‘encouraging diversity’ as ‘having targets’, and this was exactly my opinion not so long ago. My recent experiences, however, have shifted my views and the more I learn, the more I want to contribute to the cause.

Why is it so important? Because more diverse teams are generally more productive, with a better variety of ideas, and are more pleasant places to work.

In my personal experience as a consultant, I’ve also found it much more pleasant when working away from home to have another lady around to socialise with.

Targets are bad. They shift focus to hitting a goal of a number – by the right or possibly the wrong means – instead of instigating a lasting change. But encouraging diversity, particularly gender diversity, means truly understanding and tackling the underlying issues that cause IT teams to be so male-dominated – in a way that can change the industry for the better, and for the long term.

As a lady in a male-dominated industry, I’ve always worked on teams with at least one female member (obviously). Watching the first two episodes of The Apprentice this season, I found it interesting to see the marked difference between the infighting in week one – where the teams were split by gender – and week two when at least on Team Apollo, Stella took a firm hand with the bickering boys.

Over time, I do hope to see attitudes shift. One day, maybe the majority of teams I work in will be balanced between women and men!

Once upon a time, there was a developer …

I started out in my career as a developer, but it wasn’t long before frustration kicked in. Frustration, that is, with the business: they wanted everything. All at once. And sometimes, they wanted things that were daft, or pointless, in my lowly developer’s opinion.

When the opportunity came up to become a product manager, I jumped at the chance to try and influence the prioritisation process. Our team was successful to some degree (some people will never stop asking for daft things) but I missed being part of the technical implementation, and within a couple of years I found myself back in the role of a Program Manager, a special combination of business analyst and project management roles (different to the programme manager role as I understand it now).

Our team were the first in our company to try using agile techniques. In an environment that was increasingly process heavy, it wasn’t always easy. Our immediate management were both a help and a hindrance, finding creative ways to circumvent the heavier processes but ultimately not doing enough to change the company as a whole. We had the enthusiasm for Scrum but not the experience to optimise it for our team. Ultimately, I chose to move on to ThoughtWorks – a company known for promoting agile that I had long admired, and hoped would be a place to gain experience by working on many different projects.

As a business analyst in an agile team, I found that the work ebbed and flowed. I envied the developers, working on one story, with a (mostly) continuous flow of work. These people cared about what they developed, about writing good tests, long term maintenance, finding new technologies. The seed of an idea began to form in my mind – could I go back to development?

It was a scary thought. Would it be like going back to the beginning again? Would I be any good at it? Would they want to halve my pay?

It turns out that yes, it’s pretty scary, but yes, it can be done. And no, they didn’t halve my pay either. As for whether I’m any good: well, time will tell, but I don’t think I’m doing terribly badly.

I’ve been meaning to get back into blogging for some time – work and life seem to keep getting in the way – so I don’t know yet if this will be successful, but it seemed a good excuse and a good place to start!