This post walks through the steps you can take to integrate JavaScript unit testing into your Worklight hybrid application development workflow and continuous build. We will build up the continuous build environment using the Worklight command-line interface (CLI) and develop some example unit tests using the QUnit unit testing framework library, and use PhantomJS for the test execution.
We'll take it a step further to show how to integrate our continuous test execution environment in Maven. Under Maven, we get dependency management for PhantomJS and use the surefire test execution plugin, which expects JUnit-style XML test report output format.
Additionally, we'll make sure this development workflow is compatible with all of your developers' operating systems; Linux, MacOS, and Windows.
Prequisites:
-
a basic understanding of Worklight project structure plus Ant, web, and JavaScript programming
-
(optional) a basic understanding of Maven
Note that I'll show screenshots using Eclipse and Worklight Studio, but understand this is just to simplify the visualization of the project structure. Eclipse and Worklight Studio are not required for this post.
For the impatient
The complete, finished project is available for download as a zip: MyWLProject.zip. Unzip and explore it as you read, or skip this download and build up your knowledge by following the post.
If you choose to download and unzip the sample, you may optionally import it into Eclipse where you have Worklight Studio 6.2 installed. This is a convenient way to navigate through the project as you read.
Create a Worklight project and application using the CLI
Introduced in Worklight 6.2, the command-line interface is a new way of creating and interacting with Worklight projects, applications, and adapters. You can create, modify, and build entirely from the command-line.
-
Create a Worklight project:
wl create MyWLProject
-
Create a Worklight hybrid application in the new project:
cd MyWLProject
wl add hybrid MyWLHybridApp
-
At this point, we have a Worklight project and application. You can build the project with:
wl build
Write some application JavaScript code
As with any web application, you can place your JavaScript inline in an HTML document, or in any separate JavaScript file referenced by a <script>
tag in the HTML document. For simplicity, let's just add code to the main.js
file that was generated for us, which is already referenced in index.html
:
-
Open the main.js
file in your preferred editor:

-
Append the following production code to the bottom of main.js
. The use of the module pattern here is an arbitrary choice.
var MyWLApp = (function() {
'use strict';
// abstraction of a Worklight adapter procedure call:
function _getUserEmail(user, successCallback, failureCallback) {
// we abstract the WL.Client.invokeProcedure onSuccess callback a bit:
var _onSuccess = function(response) {
if (response.invocationResults.isSuccessful) {
successCallback(response.invocationResults.email);
} else {
failureCallback();
}
};
var invocationData = {
adapter: 'UserDataAdapter',
procedure: 'getUserEmail',
parameters: [user]
};
var options = {
onSuccess: _onSuccess,
onFailure: failureCallback
};
WL.Client.invokeProcedure(invocationData, options);
}
// public API:
return {
getUserEmail: _getUserEmail
};
}());
Clearly my goal here is not to show proper JavaScript coding patterns, but do take note that you can unit test how your code interacts with the Worklight API and even the network! Read on...
Set up the test infrastructure for development workflow
The names and locations of files is entirely up to you, but for the sake of consistency, and compatibility with the rest of this article, be careful to follow the conventions I've used.
-
Create folder test
as a peer folder of common
:

-
Create subfolder tests
with new empty file mainTests.js
in it:

-
Download a copy of qunit.js
and qunit.css
into the tests
folder:

-
Create mainTest.html
in the test
(not tests
!) directory. The suffix Test.html
will matter later. Here are the full contents for your cut/paste convenience.
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>MyWLHybridApp Main Tests</title>
<link rel="stylesheet" href="tests/qunit.css">
<script src="tests/qunit.js"></script>
<!-- production code under test: -->
<script src="../common/js/main.js"></script>
<!-- tests: -->
<script src="tests/mainTests.js"></script>
</head>
<body>
<div id='qunit'></div>
<div id='junit'></div>
</body>
</html>
-
Visit the mainTest.html
file in your preferred web browser to confirm the infrastructure is in place:

Looks good!
Write a test or two
QUnit output in the browser shows no tests were run, but it confirms the intial setup is good. Let's write a test! My goal here is not to teach the syntax of QUnit, so I'm just going to give some cut/paste code. You can and should do better.
First, we want to mock the Worklight API. Our intent is to test our code, not the Worklight SDK.
-
Create a mocks.js
file in the tests
folder with the following content:
var WL = WL || {};
WL.Client = WL.Client || {};
-
Add a script
tag for mocks.js
before the main.js
script tag in the mainTest.html
file:
<!-- mocks: -->
<script src="tests/mocks.js"></script>
-
Cut/paste the following into mainTests.js
:
/*globals module, asyncTest, ok, start, WL, MyWLApp*/
(function() {
'use strict';
module('main');
asyncTest('failureCallback parameter called when adapter isSuccessful false', 1, function () {
// save original to restore after mock:
var original = WL.Client.invokeProcedure;
// mock:
WL.Client.invokeProcedure = function(invocationData, options) {
options.onSuccess({invocationResults: {isSuccessful: false}});
};
// test:
var onSuccess = function() {
ok(false, 'onSuccess should not be called');
start();
};
var onFailure = function() {
ok(true, 'onFailure was called');
start();
};
MyWLApp.getUserEmail('joe', onSuccess, onFailure);
// restore the mock to its original:
WL.Client.invokeProcedure = original;
});
})();
-
Refresh your browser to see the green results:

Now your development workflow is ready for prime time. You can write production code and test code, refreshing your browser as you go. You can always integrate more sophisticated techniques and tools like test coverage measurements, filesystem listeners to auto-refresh the browser window, and mock libraries like sinon.js. These are beyond the scope of this article, as they are more for development workflow enhancements, where I'm focusing on continuous build and automated test.
Integrate the tests into your continuous build
What good are unit tests if they don't cause a build failure when the tests fail? No good, that's what! So let's put some build infrastructure in place so these tests run automatically in a continuous build.
During development, we run the tests in a browser, which renders the page in a window. Continuous build machines often lack a windows environment, and have no need for a web browser. We need a way to process the mainTest.html
file in a headless browser. This is where PhantomJS comes in.
I'm going to give you starter (or nearly complete!) scripts to execute under Ant and PhantomJS, then highlight some of the features.
-
Download and install Apache Ant
-
Download ant-contrib binary. I used version 1.0b3. Note the filesystem location of the ant-contrib-1.0b3.jar
file as we will refer to it in our Ant script. For simplicity, put it in the root project folder:

-
Download the attached resource files js-unit-tests.xml and run-qunit-phantomjs.js and place them in the root project folder:

-
At the command prompt, change directories into the root project folder and run the Ant script:
cd MyWLProject
ant -f js-unit-tests.xml
Note that the first run may take some time because the ~9mb PhantomJS executable is downloaded as part of the script execution.
-
Observe the output:
...
run-test:
[echo] Executing: /Users/rott/eclipse/runtime-EclipseApplication_MAIN/MyWLProject/target/phantomjs/bin/phantomjs run-qunit-phantomjs.js apps/MyWLHybridApp/test/mainTest.html /Users/rott/eclipse/runtime-EclipseApplication_MAIN/MyWLProject/target/surefire-reports
[exec]
[exec] STARTING MODULE: main
[exec] MODULE RESULTS: Passed: 1 Failed: 0 Total: 1 Time(ms): 17
[exec] MODULE REPORT: MyWLProject/target/surefire-reports/TEST-JavaScript-MyWLProject-test-mainTest-main.xml
[exec] RUN RESULTS: Passed: 1 Failed: 0 Total: 1 Time(ms): 34
BUILD SUCCESSFUL
...
Notice a few important items in the output:
-
I print out what is executed, so you can cut/paste it directly onto your command prompt to test the
run-qunit-phantomjs.js
script without Ant
-
Also notice the location of the QUnit module report file is shown. If the execution of your test produces console output (via
console.log
calls, for example), you'll find it here.
-
If you're really observant, you'll notice the report file goes to
target/surefire-reports/TEST-*
. The scripts I've given you are already Maven-compatible, if you use the default build and surefire locations in your Maven build!
-
If you're super-star observant, you'll also notice the QUnit output file is in JUnit XML format. It's compatible with tools that read JUnit-style XML output!
What happens if a test fails?
Not only will the build fail (which is what we want!), but the scripts print some helpful information.
-
Change one of the tests to fail intentionally. For example, in the test we wrote above:
Change:
ok(true, 'onFailure was called');
To:
ok(false, 'onFailure was called');
-
Run ant -f js-unit-tests.xml
again, and observe the output:
...
run-test:
[echo] Executing: /Users/rott/eclipse/runtime-EclipseApplication_MAIN/MyWLProject/target/phantomjs/bin/phantomjs run-qunit-phantomjs.js apps/MyWLHybridApp/test/mainTest.html /Users/rott/eclipse/runtime-EclipseApplication_MAIN/MyWLProject/target/surefire-reports
[exec]
[exec] STARTING MODULE: main
[exec] MODULE RESULTS: Passed: 0 Failed: 1 Total: 1 Time(ms): 18
[exec] MODULE REPORT: MyWLProject/target/surefire-reports/TEST-JavaScript-MyWLProject-test-mainTest-main.xml
[exec] RUN RESULTS: Passed: 0 Failed: 1 Total: 1 Time(ms): 38
[exec] TEST FAILED. Visit 'apps/MyWLHybridApp/test/mainTest.html' in your favorite browser to debug it.
BUILD FAILED
...
Notice especially the "TEST FAILED" line. The build output helpfully points you in the direction of triaging and resolving this test failure!
Features of the run-qunit-phantomjs.js script
This script is the execution script used by PhantomJS (note the "Executing" line in the output above). The script hooks to PhantomJS and QUnit callbacks to control PhantomJS execution and capture all of the QUnit output in order to produce the console and JUnit-style output. Its usage is:
phantomjs(.exe) [path-to-phantom-qunit-js] [url-of-your-qunit-testsuite] [junit-output-directory-path] [timeout-in-seconds]
Where:
-
[path-to-phantom-qunit-js]
is the filesystem path to run-qunit-phantomjs.js
-
[url-of-your-qunit-testsuite]
is the URL (filesystem path or http://) to your HTML file that runs QUnit tests
-
[junit-output-directory-path]
(optional, defaults to target/surefire-reports
) is the filesystem path to the output folder for JUnit style XML report files
-
[timeout-in-seconds]
(optional, defaults to 10) is the amount of time the HTML file is permitted to execute before PhantomJS times out
Some notable features:
-
JUnit style XML output is compatible with tools that expect it, such as the Rational Team Concert build system
-
compatible with Linux, Mac OSX, and Windows
Features of the js-unit-tests.xml Ant script
This script is the means by which we search (by way of Ant filesets) and iterate over all files suffixed with Test.html
to execute them (I told you that suffix would matter!). That way you can create multiple *Test.html
files in multiple Worklight apps in the same project, and the js-unit-tests.xml
script will find and execute them automatically by running the execution line shown in the above section.
Some notable features:
-
The ability to search and execute all
*Test.html
files
-
PhantomJS binary is conditionally downloaded (from Maven central) if it does not already exist in
target/phantomjs
-
compatible with Linux, Mac OSX, and Windows
Wrapping it all up with Maven (optional)
Already, you have many options for executing these tests in a continous build. You could drive it all with a shell script, Ant, Grunt, or any other build executor. In this article, we've put a few paths in place that are compatible with Maven, but don't require Maven for execution. We've also chosen to download the PhantomJS binary from Maven central and place it under target/phantomjs directory. This forethought has the advantage of being immediately compatible and network-friendly (no extraneous downloading!) to Maven's dependency management.
-
Download the attached resource file pom.xml
and place it in the root project folder:

The pom.xml
file uses Maven profiles to detect which operating system on which the maven binary is currently running so the appropriate PhantomJS dependency is downloaded. The PhantomJS binary is placed in target/phantomjs
prior to executing the js-unit-tests.xml
file under the maven-antrun-plugin. Now the conditional PhantomJS download in js-unit-tests.xml
is already satisfied by the Maven dependency.
-
Download and install Apache Maven
-
At the command prompt, change directories into the root project folder and run Maven:
cd MyWLProject
mvn clean install
-
Inspect the output
Some notable features:
-
Uses Maven dependency management to download PhantomJS binary for the current operating system
-
Has a
notest
profile to skip tests when desired in a larger multi-module build. Execute by mvn clean install -Pnotest
As a bonus, I've included JSHint plugin section in the pom.xml
for a zero-effort inclusion of JSHint execution against our production code. This enforces stylistic consistency among all the developers contributing into the JavaScript codebase. Your developers are using JavaScript editors with integrated JSHint rule violation highlighting, right? You'll want to match up the JSHint rules listed in the pom.xml
file with the rules in your developers' editors or vice versa.
Other considerations
When to execute JSHint?
I've included JSHint in pom.xml so that it executes automatically in Maven builds, but if you're not using Maven, or want a bit more flexibility, you'll probably want to move that JSHint enforcement into the js-unit-tests.xml
Ant script. There are many resources on the internet to help you execute JSHint in an Ant script or by command line, so I won't repeat them here.
How can I handle Maven surefire plugin's "surefire.report.dir" property?
Recall that our scripts assume they should write the JUnit style XML output to target/surefire-reports
. When you run a multi-module Maven build, and want to aggregate all of the JUnit reports into one directory, you configure the surefire plugin with the surefire.report.dir
property. You will need to read and honor that property in your code logic in the js-unit-tests.xml
Ant script. Properties in maven are passed to Ant scripts executed by the Maven antrun plugin automatically. You can pass additional property values by changing the pom.xml
file like:
<ant antfile="${basedir}/js-unit-tests.xml">
<property name="other.property" value="${my.other.property.key}"/>
</ant>
How can I run the same tests on a device?
If you've been careful in your development to mock properly (remember the mocks.js
file we made?) and your logic in your QUnit tests is smart, you can copy that test
folder over into the web resources folder in the Worklight native environment project (assets/www/
on Android, for example), and temporarily change the native code to load the *Test.html
file (or use some smart logic to allow beta testers to load up the test file).
For example, on Android, change the main Activity class from:
super.loadUrl(WL.getInstance().getMainHtmlFilePath());
To:
super.loadUrl("file:///android_asset/www/test/testMain.html");
Be careful to run the tests against a test server rather than a production server, if the tests demand such connectivity when the mocks are removed.
This consideration sounds like a good candidate for a part two blog post!
Summary
Now we have a development workflow that promotes the use of JavaScript unit testing compatible with Worklight hybrid applications. We built up a continuous build strategy that executes these same tests, and wrapped it all up in Maven. As a bonus, you saw JSHint integration and how you might take those tests to the native app environment and execute them there.