Wednesday, March 01, 2006

Unit Testing JavaScript from within Java

(Reposted for the benefit of the Agile Planetters)

So you're developing a web app. You're probably using Java. You might even be using some of that super cool AJAX. But with all that logic on the client side, you better be sure you're testing it.

You could use JsUnit - that's an excellent solution, as it can be run from multiple real browsers ensuring the code works the way it should with that particular javascript implementation. Unfortunately, we don't want to have to do separate tests - unit testing with JUnit, and javascript unit testing with a browser. Since these are true unit tests of javascript code, it would be nice to integrate it into our JUnit testing cycles. Another benefit of testing javascript from within java is that we can use dynamically generated mocks to provide simulated collaborators - just like standard java tests can.

An alternative to using JsUnit and a browser, is to use Mozilla Rhino (http://www.mozilla.org/rhino/) to interpret the javascript from within Java. This should give you good confidence that the logic is implemented correctly. This allows for rapid feedback during your TDD sessions. No browser windows to pop up, nothing fancy to set up on the continuous integration side, and it's cross platform. And best of all, we can mock out any objects the script expects. Note that you should test your logic code here, I would recommend to keep the browser-specific code (stuff manipulating truly browser specific objects) tested in JsUnit.

Note that this route doesn't provide you with the standard objects that a browser would normally provide - no access to the document, etc. It would just be used for "logic" routines. I suggest having a split between logic routines, and ones which interact with the browser - and use JsUnit or WATIR/Selenium/PAMIE to test that the browser routines work properly.

For my javascript JUnit test, I've done the following:

protected void setUp() throws Exception {
super.setUp();
ctx = Context.enter();
scope = ctx.initStandardObjects(null);
}


protected void tearDown() throws Exception {
try
{
Context.exit();
}
finally
{
super.tearDown();
}
}


This ensures that the (thread local) javascript context is initialized/cleaned up properly.

You then wish to import the script under test into the context:

ctx.evaluateReader(scope, getReaderForResource("TestJavaScript.js"), "TestJavaScript.js", 0, null);

Get the Function placeholder for the function we will call:

Object functionRtnObj = scope.get("anyFunction", scope);
assertTrue(functionRtnObj instanceof Function);
Function functionObj = (Function) functionRtnObj;

Make the actual call with supplied parameters:

Object result = functionObj.call(ctx, scope, scope, new Integer[] {new Integer(1)});

Do some assertions on the return value:

assertNotNull(result);
assertEquals("anyReturnValue", result);

That's it!

NOTE: we cannot create javascript unit tests using JsUnit and run them from within java by using Rhino because it depends on various browser specific objects. An option (which I haven't verified yet) might be to write your tests in JsUnit, and use HtmlUnit (which uses Rhino for javascript) to simulate a browser. This way we can run the tests through a quick java system (suitable for a continuous integration system), and through various real browsers. All (or most - HtmlUnit is by no means complete) of the normal browser objects should be available. No mocks though :)

Enjoy!

No comments: