Unit testing Javascripts in Java 6 – Getting started with the javax.script api

5

I intended to reduce
the things I do with Javascript. But maybe Javascript isn’t that bad when you
can run it inside java. I’m still working on a very ‘interesting’  project with asp and Javascript files. Since I didn’t create a single JUnit
for two weeks I thought it was time to unit test an updated Javascript. Replace
var with int and I was almost done.

But then I recalled
that I read a book about Java 6 and I didn’t have to change a single line of
Javascript.

....

The script I had to
test was very simple, but I needed my Unit test so badly. The date was
formatted in the “Days: %1, Hours: %2, Minutes: %3, Seconds: %4” notation. But
the customer wanted to omit the entries when the value was null or 0. So when
you only pass 5 minutes to the method you want to display “Minutes: 5”.

 

I had to change an
existing script, I made some changes and came up with the following (I added an
error on purpose by the way, we’ll find it with the unit test later)

&nbsp;function format() {<br />    var str = &quot;&quot;;<br />    var array = [this.nDays, this.nHours, this.nMinutes,<br />                                          this.nSeconds];<br />    var headerArray = [L_Info_DurationDays,<br />                       L_Info_DurationHours,<br />                       L_Info_DurationMins,<br />                       L_Info_DurationSecs];<br /><br />    for (var i = 0; i &lt; 4; i++) {<br />        if (array[i]!= null &amp;&amp; array[i]&gt; 0) {<br />            str += &quot;,, &quot; + headerArray[i]+ &quot;: &quot; <br />                                         + array[i];<br />        }<br />    }<br /><br />    return str.substr(2); //skip first comma and space<br />}

 

The headers contain the
text “Days”, “Hours”, “Minutes” and “Seconds” and these values come from an
external Javascript file (we have one file for every language in the
application).

I saved this script in
the src/main/js/format.js directory. Now create a unit test with an empty test method
and add the following code:

FileReader reader = new FileReader(&quot;src/main/js/format.js&quot;);<br /> <br />ScriptEngineManager manager = new ScriptEngineManager();<br />ScriptEngine engine = manager.getEngineByName(&quot;javascript&quot;);<br /> <br />//init .js file<br />engine.eval(reader);<br /><br />

You have to initialize
a ScriptEngineManager and get the Javascript engine of it. Now you can evaluate
Javascripts inside java! Yes, it’s that easy and the error messages are better
than in your browser.

Now you can run your
unit test, but it does nothing because we have to make a call to the format
function.

Use eval(“format()”)
to invoke the format function. The eval method returns a java object, this is a
String (because the Javascript returns a String).

 

When we run the test
an error occurs:

ReferenceError:
"L_Info_DurationDays" is not defined. (<Unknown source>#5) in
<Unknown source> at line number 5

 

Unknown sounds good,
doesn’t it? Ok, since we only have one file it is easy to find. The error is
also on line 5 (that’s quite a surprise for me because in Internet Explorer the
most unlikely place to find the error is the line number the browser passes in
the error message)

All right, a missing
variable must be added. You can do this by entering eval(“var
L_Info_DurationDays=’Days’;”); But there is a better solution. You can pass
your java objects into Javascript!

engine.put(“L_Info_DurationDays”,”Days”);
also does the trick. When you pass an array of objects this array will appear
as an array in Javascript, very nice!

Update the values for
hours, minutes and seconds and continue. No errors! That’s strange because
there are some missing variables (the ones in the array that start with this).
These variables can be passed too with put(), just omit the this:

Integer[] array = new Integer[]{0, 10, 10, 0};<br />engine.put(&quot;nDays&quot;, array[0]);<br />engine.put(&quot;nHours&quot;, array[1]);<br />engine.put(&quot;nMinutes&quot;, array[2]);<br />engine.put(&quot;nSeconds&quot;, array[3]);<br /><br /> <br />

Now we can start
create some tests. The first test is to pass 10 hours and 10 minutes and check
whether “Hours: 10, Minutes: 10 is returned”

array = new Integer[]{0, 10, 10, 0};<br />String result = (String) engine.eval(“format();”);<br />assertEquals(“Hours: 10, Minutes: 10 is returned &quot;, result);<br />

 

When we run the unit
test we will get the following error:

junit.framework.ComparisonFailure:

Expected :Hours: 10,
Minutes: 10

Actual  : Hours: 10,, Minutes: 10

 

Wow, you didn’t expect
that did you? Remove the second comma in the format.js and you unit test should
run fine.

 

I thought my unit test
would also break on the substr(2) on a String of length 0, but it didn’t. Maybe
it’s a feature of Javascript, but in Java you will definitely get an
IndexOutOfBoundsException. Just to be sure I added a check for the String
length into my method. The Mozilla Javascript guide doesn’t mention anything
about this issue: http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:String:substr

And when I enter “”.substr(2)
in my Firefox it just returns an empty String. So I guess Javascript is a
little bit nicer in some cases.

 

Debugging

It’s not possible to
debug the Javascript, it’s just a String that is read from a file. But you can
see some things with the debugger of your IDE that can be very useful:

 

 

When you expand the
engine, context and engineScope objects you will see a list of functions you
can call. So you can at least see that your function is loaded and which
functions are available. I also found the print function this way. It prints to
the standard out and you can put some debugging info inside your Javascripts.

 

Conclusion

This was a relatively
easy script to debug. It gets tougher when you have references to objects you
didn’t create yourself (like the document object of request/response objects in
server side Javascript). I’m afraid you have to work with something like subs.

Debugging is also not
possible. Maybe if you create a line in java for every line of Javascript code
(wrap every line of code in an eval). But that’s not the way to do it.

When you’re coding
Javascript and want to unit test it in java you have to make sure your methods
perform small and simple tasks, but you already did that because you read the
refactoring book I told you about yesterday. Also try to pass around external
objects as little as possible.

Javasript became a
little bit less evil with this new java feature, when I have so spare time I’ll
try to integrate the java testing with libraries like Dojo or JQuery. And maybe
some people will create stubs for the browser objects that are available
(document, window, etc. )

 

It’s also possible to
user other scripting lanaguages, please visit https://scripting.dev.java.net/ for
more information. I also saw some things about using Java objects inside
Javascript, but that’s beyond the scope of this article, more information can
be found on http://java.sun.com/javase/6/docs/technotes/guides/scripting/programmer_guide/index.html.

Share.

About Author

5 Comments

  1. Jeroen van Wilgenburg on

    That’s a good point, I still have to get used to the Javascript inside Java, it’s stil scary ;-)
    Theoretically the array of int’s can contain Strings in the Javascript.

  2. Next time I’ll execute a script before posting… the a1 and a2 inside the push method should be switched to get the same result as the example above.

  3. I know it’s totally not the point, but if you’d written the method without magic numbers… and using javascripts build-in functions for imploding arrays it would have been far less error prone:

    var a1 = [0,10,10,0]
    var a2 = [‘days’,’hours’,’minutes’,’seconds’]

    var t = [];
    for( i in a1 ) {
    if(a1[i] > 0){
    t.push([a1[i] + ‘: ‘+ a2[i]]);
    }
    }

    t.join(‘, ‘);