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.

App-celerating!

Appcelerator TitaniumI first tried out Appcelerator Titanium Mobile at Launch 48, with the help of some of the guys from PayPal, who had recently teamed up with Appcelerator to work on the platform.

My initial impressions were very positive, it was reasonably easy to install and the Kitchen Sink example app had some pretty slick looking stuff. I’ve played quite a bit with PhoneGap and JQTouch which is also an impressive platform, but tricky at times to create a UI that looked and behaved as smoothly as the native apps. Since Titanium compiles the JavaScript down to native code, the app looks exactly the same as a native one – and there is much less styling work needed, in fact, I was really surprised by how little code was required to get a simple app up and running.

Kitchen Sink Screenshots

Screenshot of Base UI Options in Kitchen Sink

Screenshot of Pickers in Kitchen Sink

Screenshot of Sliders in Kitchen Sink

Screenshot of Twitter in Kitchen Sink


On the downside: I’ve heard anecdotally that many people have issues installing Titanium and getting it running. It does have it’s bugs: I ran into one at Launch48 with swiping on a view within a table row inexplicably crashing the app, but I didn’t have time to figure out the root cause.

One thing it is definitely well suited for is rapid prototyping – so far I’ve been able to create a simple cover flow with about 20 lines of JavaScript code. With a better knowledge of the platform, I think I could produce a prototype more quickly than an Omnigraffle wireframe sketch – just imagine the possibilities!

There are lots of tutorials and resources on the web, so here are a few good links for getting started – and some of the ‘gotchas’ that I ran into so far.

SDKs

The website says that Titanium requires either the iPhone or Android (1.5) SDKs to compile an app, but for me the New Project button was disabled until I installed both. It usually detects when they are installed, but you can also point it to the directory.

Android installation instructions

Download iOS from the Apple iOS Developer centre– you’ll need an account but it’s free (unless you want to deploy to a real device, in which case it’s $100 :().

Install Titanium

Download Titanium from the Appcelerator site download page

This includes the Mobile and Desktop versions. Once it’s downloaded and installed, open the app and click the New Project button. Choose Mobile or iPad from the Project Type dropdown.

I had an issue where I could only choose Desktop initially, but restarting the application seemed to solve this (no idea why, but I’ve had no problems since).

Titanium will most likely tell you to download the new Mobile SDK 1.4.2. This does not work for creating new Mobile projects, although it seems to be fine for creating new iPad projects. Choose the 1.2.0 SDK version and it will create a new Mobile project.

Getting Started

Creating a new project will give you a basic app with one Javascript file: app.js, inside the Resources folder. The app contains a TabGroup and two Windows. Windows are the basic unit within the app, and they can contain tables, views and other UI elements.

I found a useful tutorial for understanding the differences between Windows, Views and Tab Groups.

The Titanium Developer documentation is useful for finding out the names and properties for the different objects – as a rule, most UI objects can be created by something like this (replace ObjectName with the object name, property: value with the relevant properties and values):

Titanium.UI.createObjectName({property:value})

The Stack Overflow-style Developer Q&A section is also really helpful.

Getting the App on to the iPad

So far I’ve only put my app on to my iPad. I needed a Development Certificate and Provisioning Profile with the correct app ID (which I had to download from the Apple Developer centre – and jeez what a pain in the butt that was!).

Within Appcelerator, go to the ‘Run on Device’. You’ll need to click on ‘Upload’ to add your Provisioning Profile. Appcelerator will detect when it has all of the necessary stuff installed, and only then will the ‘Install Now’ button become available.

Make sure you’ve got the right SDK or you’ll get an install error. Choose the right Development Certificate and Provisioning Profile. I’ve got more than one, and it caused an entitlement error when I picked the wrong one and tried to install the app on my iPad.

Then click Install Now, and the app is installed via iTunes. Voila!