The last time I used JsUnit was when I first joined Talis. At the time my colleague Ian Davis asked me to write a JavaScript client library for one of our platform API’s to make it easy for developers to perform bibliographic searches. It wasn’t a particularly difficult task and I did it relatively easily. It was around the same time that Rob was extolling the virtues of Test Driven Development to me, and to try to prove his point we agreed to do an experiment: he asked me to set aside the library I had written and to see if I could develop the library again using test driven development. It meant I had to figure out how to unit test JavaScript, and thats when I found JsUnit. I did the exercise again and even I was impressed with the results. By having to think about the tests first, and design the interface to the library as I wrote each test it evolved very differently to my original solution. Consequently it was also far superior.
Anyway fast forward two and half years and I find myself in a similar situation. We have only just begun to start writing bits of JavaScript code based around prototype.js to help us create richer user experiences in our products if we detect that JavaScript is enabled in the browser. This now means I want to ensure that we are using the same rigour when writing these bits of code as we do in all other parts of the application – just because its JavaScript and executed inside the browser this doesn’t mean it shouldn’t be tested.
I’ve just spent the morning getting JsUnit installed and figuring out how to get it to run as part of a continuous integration process, as well as thinking about how to write tests for some slightly different scenarios. Here’s what I’ve discovered today:
Installing JsUnit
Couldn’t be easier … go to www.jsunit.net and download the latest distribution, and extract into a folder on your system somewhere, lets say
/jsunit
for now. The distribution contains both the standard test runner as well as jsunit server which you will need it if you want to hook it into an ant build.
Writing Tests
In JsUnit we place our tests in a HTML Test Page which is the equivalent of a Test Class, this test page must have a script reference to the jsUnitCore.js so the test runner knows its a test. So lets work through a simple example. Let’s say we want to write a function that returns the result of adding two parameters together. The Test Page for this might look like this:
-
-
<html>
-
<head>
-
<title>Test Page for add(value1, value2)</title>
-
<script language="javascript" src="/jsunit/app/jsUnitCore.js"></script>
-
<script language="javascript" src="scripts/addValues.js"></script>
-
</head>
-
<body>
-
<script language="javascript">
-
function testAddWithTwoValidArguments() {
-
assertEquals("2 add 3 is 5", 5, add(2,3) );
-
}
-
</script>
-
</body>
-
</html>
-
For now lets save this file to /my-jsunit-tests/addTest.html
To run the test you need to point your browser at the following local url:
file:///jsunit/testRunner.html?testPage=/my-jsunit-tests/addTest.html
The test will not run since we haven’t defined the add function. Let’s do that (very crudely):
Now if you go to that URL it will run the test and report that it passed. Excellent, we’ve written a simple test in JavaScript. Now lets extend this a little, lets say I want to write something more complicated like a piece of JavaScript that uses Prototype.js to update the DOM of a page. Is this possible? Can I do that test first? It turns out that you can …
Lets say we have a div on the page called ‘tableOfContents’ and we want to use Prototype.js to dynamically inject a link onto the page that says [show] and lets say we want to write a function that will toggle this link to say [hide] when the user clicks on it, this link will also set the visible state of the table of contents itself which for now we’ll say is just an ordered list (OL). Our test page is going to be slightly more complex …
-
-
<html>
-
<head>
-
<title>Test Page for multiplyAndAddFive(value1, value2)</title>
-
<script language="javascript" src="/jsunit/app/jsUnitCore.js"></script>
-
<script language="javascript" src="scripts/prototype/prototype-1.6.0.2.js"></script>
-
<script language="javascript" src="scripts/tableOfContents.js"></script>
-
</head>
-
<body>
-
<div id="tableOfContents">
-
<h2 id="tableOfContentsHeader">Table of contents</h2>
-
<ol id="list-toc">
-
</ol>
-
</div>
-
<script language="javascript">
-
function testTOC()
-
{
-
var title = $(‘lnkToggleTOC’).title;
-
assertEquals("should be Show the table of contents", "Show the table of contents", title);
-
-
toggleTableOfContents();
-
-
var title = $(‘lnkToggleTOC’).title;
-
assertEquals("should be Hide the table of contents", "Hide the table of contents", title);
-
-
}
-
</script>
-
</body>
-
</html>
-
There are some differences in this test. Firstly the html contains some markup, that I’m using as the containers for my table of contents. The table of contents has a header and the contents in the form of an empty ordered list. Now I know that I want the javascript to execute when the page is loaded, so I’ve written this test to assume that the script will run and will inject and element called ‘linkToggleToc’ which is the show/hide link next to the heading. Therefore the first line of the test uses prototype.js element selector notation to set a local variable called title to the value of the title of the element that has the id ‘linkToggleToc’. If the script failes to execute then this element will not be present and the subsequent assert will fail. If the assert succeeds, then we call the toggleTableOfContents function and then repeat the same evaluation only now we are checking to see if the link has been changed.
The code for tableOfContents.js is as follows:
-
span class=”st0″>’load’‘Show the table of contents’‘Hide the table of contents’‘list-toc’).hide();
-
$(‘tableOfContentsHeader’‘inline’‘a’, { ‘id’: ‘lnkToggleTOC’, ‘href’: ‘javascript:toggleTableOfContents()’, ‘title’: titleShowTOC, ‘class’: ‘ctr’ }).update("[show]");
-
-
$(‘tableOfContentsHeader’‘after’‘list-toc’‘lnkToggleTOC’).update(‘[show]’);
-
$(‘lnkToggleTOC’‘lnkToggleTOC’).update(‘[hide]’);
-
$(‘lnkToggleTOC’).title = titleHideTOC;
-
}
-
}
Now if we run this test in the same way we executed the previous test it will pass. I accept that this example is a bit contrived since I know it already works and I’ve skimmed over some of the details around it. The point I’m trying to make though is that you can write unit tests for pretty much any kind of JavaScript you need to write, even tests for scripts that do dom manipulation, or make AjaxRequests etc.
Setting up the JsUnit server so you can run it in a build
JsUnit ships with its own ant build file that requires some additional configuration before you can run the server. The top of the build file contains a number of properties that need to be set, here’s what you set them to ( using the paths that I’ve been using in the above example)
-
-
<project name="JsUnit" default="create_distribution" basedir=".">
-
-
<property
-
name="browserFileNames"
-
value="/usr/bin/firefox-2" />
-
-
<property
-
id="closeBrowsersAfterTestRuns"
-
name="closeBrowsersAfterTestRuns"
-
value="false" />
-
-
<property
-
id="ignoreUnresponsiveRemoteMachines"
-
name="ignoreUnresponsiveRemoteMachines"
-
value="true" />
-
-
<property
-
id="logsDirectory"
-
name="logsDirectory"
-
value="/my-jsunit-tests/results/" />
-
-
<property
-
id="port"
-
name="port"
-
value="9001" />
-
-
<property
-
id="remoteMachineURLs"
-
name="remoteMachineURLs"
-
value="" />
-
-
<property
-
id="timeoutSeconds"
-
name="timeoutSeconds"
-
value="60" />
-
-
<property
-
id="url"
-
name="url"
-
value="file:///jsunit/testRunner.html?testPage=/my-jsunit-tests/tocTest.html" />
-
</project>
-
You can then type the following command in the root of the jsunit distribution to launch the jsunit server, executes the test, and outputs a test results log file, formatted just like JUnit, and reports that the build was either successful or not if the test fails.
ant standalone_test
Remember that in this example I’ve used a simple Test Page, however JsUnit, like any XUnit framework allows you to specify Test Suites, which is how you would run multiple Test Pages. Also the parameters in the build file woudn’t be hardcoded in you continuous integration process but would rather be passed in, and you would want to call it from your projects main ant build file … all of which is pretty simple to configure, once you know what is you want to do and what’s possible.