A broken JavaScript code example
One of the biggest challenges facing web applications is the support of web browsers with different versions. JavaScript code running on Safari does not necessarily work on Windows® Internet Explorer (IE), Firefox, or Google Chrome. This challenge is inherited from the lack of testing the JavaScript code living in the presentation tier from day one. Without unit testing this code, organizations may pay for repeated testing of web applications after upgrading or supporting new browsers. This article shows you how to reduce testing costs using efficient unit testing for JavaScript code.
One of the common use cases is to have a login form JavaScript validation. Consider the form in Listing 1.
Listing 1. The login form
<FORM> <table> <tr> <td>Username</td> <td><input type="text" id="username"/></td> <td><span id="usernameMessage"></span></td> </tr> <tr> <td>Password</td> <td><input type="password" id="password"/></td> <td><span id="passwordMessage"></span></td> </tr> <tr> <td><input type="button" onclick="new appnamespace. ApplicationUtil().validateLoginForm()" value="Submit"/></td> </tr> </table> </FORM> |
The form is simple. It consists of username and password fields. When
clicking the submit button, a specific form validation is performed
through ApplicationUtil. This is a JavaScript
object responsible for validating the HTML form. Listing 2 shows the code of the
ApplicationUtil object.
Listing 2. The ApplicationUtil object broken code
appnamespace = {};
appnamespace.ApplicationUtil = function() {};
appnamespace.ApplicationUtil.prototype.validateLoginForm = function(){
var error = true;
document.getElementById("usernameMessage").innerText = "";
document.getElementById("passwordMessage").innerText = "";
if (!document.getElementById("username").value) {
document.getElementById("usernameMessage").innerText =
"This field is required";
error = false;
}
if (!document.getElementById("password").value) {
document.getElementById("passwordMessage").innerText =
"This field is required";
error = false;
}
return error;
};
|
In Listing 2, the ApplicationUtil object
provides a simple validation. It verifies that the username and password
fields are filled. If a field is empty, an error message stating
This field is required appears.
Although the previous code will work on Internet Explorer 8 and Safari
5.1, it will not work on Firefox 3.6 because the
innerText property is not supported on Firefox.
Often, the main issue (in the previous code and other similar JavaScript
code) is that it is not easy to detect whether the written JavaScript code
is cross-browser compatible.
One solution for this problem is to have automated unit testing that verifies the correctness of the code across browsers.
One of the best JavaScript unit testing frameworks is the JsTestDriver library, which provides testing for JavaScript code across browsers. Figure 1 shows the architecture of the JsTestDriver.
Figure 1. JsTestDriver architecture
The server is responsible for loading the JavaScript test cases runner code in the different browsers once they are captured. The browser can be captured through the command line or by pointing the browser to the server URL. Once the browser is captured, it is then called a slave browser. The server loads the JavaScript code, executes the test cases on every browser, and returns the results to the client.
The client (command line) needs two main items:
- JavaScript files — source files and test files, and
- Configuration file — for organizing the loading of the source files and the testing files.
This architecture is flexible, allowing a single server to capture any number of browsers from other machines in the network. For example, it can be useful if your code is running on Linux and you want to run your test cases against Microsoft Internet Explorer on another Windows machine.
To start working with the JsTestDriver library, download the latest version of JsTestDriver 1.3.2.
Now, let's write the JavaScript test cases. For the purpose of simplicity, I will test the following cases:
- An empty value for user name and an empty value for password.
- An empty value for user name and a non-empty value for password.
- A non-empty value for user name and an empty value for password.
Listing 3 shows part of the code of the
ApplicationUtilTest object that represents the
TestCase object.
Listing 3. Part of the ApplicationUtilTest object code
ApplicationUtilTest = TestCase("ApplicationUtilTest");
ApplicationUtilTest.prototype.setUp = function () {
/*:DOC += <FORM action=""><table><tr><td>Username</td><td>
<input type="text" id="username"/></td><td><span id="usernameMessage">
</span></td></tr><tr><td>Password</td><td>
<input type="password" id="password"/></td><td><span id="passwordMessage"
></span></td></tr></table></FORM>*/
};
ApplicationUtilTest.prototype.testValidateLoginFormBothEmpty = function () {
var applicationUtil = new appnamespace.ApplicationUtil();
/* Simulate empty user name and password */
document.getElementById("username").value = "";
document.getElementById("password").value = "";
applicationUtil.validateLoginForm();
assertEquals("Username is not validated correctly!", "This field is required",
document.getElementById("usernameMessage").innerHTML);
assertEquals("Password is not validated correctly!", "This field is required",
document.getElementById("passwordMessage").innerHTML);
};
|
The ApplicationUtilTest object is created
through the JsTestDriver TestCase object. If
you are familiar with the JUnit framework, you will be familiar with the
setUp and the
testXXX methods. The
setUp method is used for initializing the test
case. For this example, I used it to declare the HTML fragment that will
be used for the rest of the test case methods.
The DOC annotation is a JsTestDriver convention
easily used for declaring an HTML fragment.
In the testValidateLoginFormBothEmpty method,
an ApplicationUtil object is created for use
inside the test case method. The code then simulates entering an empty
user name and password by retrieving their DOM elements and setting their
values to empty values. The validateLoginForm
method is called to perform the actual form validation. Finally, the
assertEquals is called to make sure that the
message in the usernameMessage and the
passwordMessage span elements are correct, that
being: This field is required.
In JsTestDriver, you can use the following constructs:
-
fail("msg")- indicates that the test must fail and the message parameter will be displayed as an error message. -
assertTrue("msg", actual)— asserts that the actual parameter is true. If not, the message parameter will be displayed as an error message. -
assertFalse("msg", actual)— asserts that the actual parameter is false. If not, the message parameter will be displayed as an error message. -
assertSame("msg", expected, actual)— asserts that the actual parameter is the same as the expected parameter. If not, the message parameter will be displayed as an error message. -
assertNotSame("msg", expected, actual)— asserts that the actual parameter is not the same as the expected parameter. If not, the message parameter will be displayed as an error message. -
assertNull("msg", actual)— asserts that the actual parameter is null. If not, the message parameter will be displayed as an error message. -
assertNotNull("msg", actual)— asserts that the actual parameter is not null. If not, the message parameter will be displayed as an error message.
The code of the other methods includes the other test cases. Listing 4 shows the full code of the test case object.
Listing 4. The ApplicationUtil object complete code
ApplicationUtilTest = TestCase("ApplicationUtilTest");
ApplicationUtilTest.prototype.setUp = function () {
/*:DOC += <FORM action=""><table><tr><td>Username</td><td>
<input type="text" id="username"/></td><td><span id="usernameMessage">
</span></td></tr><tr><td>Password</td><td>
<input type="password" id="password"/></td><td><span id="passwordMessage"
></span></td></tr></table></FORM>*/
};
ApplicationUtilTest.prototype.testValidateLoginFormBothEmpty = function () {
var applicationUtil = new appnamespace.ApplicationUtil();
/* Simulate empty user name and password */
document.getElementById("username").value = "";
document.getElementById("password").value = "";
applicationUtil.validateLoginForm();
assertEquals("Username is not validated correctly!", "This field is required",
document.getElementById("usernameMessage").innerHTML);
assertEquals("Password is not validated correctly!", "This field is required",
document.getElementById("passwordMessage").innerHTML);
};
ApplicationUtilTest.prototype.testValidateLoginFormWithEmptyUserName = function () {
var applicationUtil = new appnamespace.ApplicationUtil();
/* Simulate empty user name and password */
document.getElementById("username").value = "";
document.getElementById("password").value = "anyPassword";
applicationUtil.validateLoginForm();
assertEquals("Username is not validated correctly!",
"This field is required", document.getElementById("usernameMessage").innerHTML);
assertEquals("Password is not validated correctly!",
"", document.getElementById("passwordMessage").innerHTML);
};
ApplicationUtilTest.prototype.testValidateLoginFormWithEmptyPassword = function () {
var applicationUtil = new appnamespace.ApplicationUtil();
document.getElementById("username").value = "anyUserName";
document.getElementById("password").value = "";
applicationUtil.validateLoginForm();
assertEquals("Username is not validated correctly!",
"", document.getElementById("usernameMessage").innerHTML);
assertEquals("Password is not validated correctly!",
"This field is required", document.getElementById("passwordMessage").
innerHTML);
};
|
Configure the different browsers with the tests
One of the recommended practices of testing JavaScript code is to put the JavaScript source code in a different folder than the JavaScript test code. For the example shown in Figure 2, I named the JavaScript source folder "js-src", and the JavaScript test folder "js-test", under the "js" parent folder.
Figure 2. The JavaScript testing folder structure
After structuring both the source and test folders, you have to provide
the configuration file. By default, the
JsTestDriver runner looks for a configuration
file named jsTestDriver.conf. You can change the name of the configuration
file from the command line. Listing 5 shows the
JsTestDriver configuration file content.
Listing 5. The JsTestDriver configuration file content
server: http://localhost:9876 load: - js-src/*.js - js-test/*.js |
The configuration file is written in the YAML format. The
server directive specifies the address of the
test server while the load directive indicates
which JavaScript files to load into the browsers and in which order.
Now, let's run the test case class on IE, Firefox and Safari browser.
To run the test case classes, we need to start the server. The
JsTestDriver server can be started using the
following command line:
java -jar JsTestDriver-1.3.2.jar --port 9876 --browser "[Firefox Path]",
"[IE Path]","[Safari Path]"
|
Using this command line, the server will start in Port 9876, and will capture the Firefox, IE, and Safari browsers on your machine.
After launching and capturing the browsers, you can run the test case classes by using the following command line:
java -jar JsTestDriver-1.3.2.jar --tests all |
After running the command, you will see the first run results, as shown in Listing 6.
Listing 6. The First Run Results
Total 9 tests (Passed: 6; Fails: 3; Errors: 0) (16.00 ms)
Firefox 3.6.18 Windows: Run 3 tests (Passed: 0; Fails: 3; Errors 0) (8.00 ms)
ApplicationUtilTest.testValidateLoginFormBothEmpty failed (3.00 ms):
AssertError: Username is not validated correctly! expected "This field
is required" but was "" Error("Username is not validated correctly!
expected \"This field is required\" but was \"\"")@:0()@http://localhost
:9876/test/js-test/TestApplicationUtil.js:16
ApplicationUtilTest.testValidateLoginFormWithEmptyUserName failed (3.00 ms):
AssertError: Username is not validated correctly! expected "This field is
required" but was "" Error("Username is not validated correctly! expected
\"This field is required\" but was \"\"")@:0()@http://localhost:9876/test
/js-test/TestApplicationUtil.js:29
ApplicationUtilTest.testValidateLoginFormWithEmptyPassword failed (2.00 ms):
AssertError: Password is not validated correctly! expected "This field is
required" but was "" Error("Password is not validated correctly! expected
\"This field is required\" but was \"\"")@:0()@http://localhost:9876/test/
js-test/TestApplicationUtil.js:42
Safari 534.50 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (2.00 ms)
Microsoft Internet Explorer 8.0 Windows: Run 3 tests (Passed: 3; Fails: 0;
Errors 0) (16.00 ms)
Tests failed: Tests failed. See log for details.
|
Notice in Listing 6 that the main issue is with Firefox. The tests run successfully on both Internet Explorer and Safari.
Fix the JavaScript code, and re-run the test cases
Let's fix the broken JavaScript code. We will use
innerHTML instead of
innerText. Listing 7
shows the fixed code of the ApplicationUtil
object.
Listing 7. The ApplicationUtil object fixed code
appnamespace = {};
appnamespace.ApplicationUtil = function() {};
appnamespace.ApplicationUtil.prototype.validateLoginForm = function(){
var error = true;
document.getElementById("usernameMessage").innerHTML = "";
document.getElementById("passwordMessage").innerHTML = "";
if (!document.getElementById("username").value) {
document.getElementById("usernameMessage").innerHTML =
"This field is required";
error = false;
}
if (!document.getElementById("password").value) {
document.getElementById("passwordMessage").innerHTML =
"This field is required";
error = false;
}
return error;
};
|
Re-run the test case object using the
--test all command line argument. Listing 8 shows the second run results.
Listing 8. The second run results
Total 9 tests (Passed: 9; Fails: 0; Errors: 0) (9.00 ms) Firefox 3.6.18 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (9.00 ms) Safari 534.50 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (5.00 ms) Microsoft Internet Explorer 8.0 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (0.00 ms) |
As shown in Listing 8, the JavaScript code is now working fine on IE, Firefox, and Safari.
In this article, you've learned how to test a JavaScript application code on different browsers using one of the most powerful JavaScript unit testing tools (JsTestDriver). You know what JsTestDriver is, how to configure it, and how to use it inside a web application to ensure the quality and the robustness of the JavaScript code of your application.
| Description | Name | Size | Download method |
|---|---|---|---|
| The source code | simple.zip | 3.35MB | HTTP |
Information about download methods
Learn
- Check out JUnit.org for more on using the JUnit testing framework.
- Learn more about YAML, a human friendly data
serialization standard for all programming languages.
- Find extensive how-to information, tools,
and project updates to help you develop with open source
technologies and use them with IBM products.
- Stay current by participating in developerWorks technical events and webcasts.
- Follow developerWorks on
Twitter tweets.
- Listen to developerWorks
podcasts for interesting interviews and discussions about software
development.
Get products and technologies
- Since you are
interested in application testing, try our
IBM Rational Functional Tester trial software.
- Go to the
JsTestDriver download page for the latest code, plugins, and more.
- Check out another
tool for unit testing, the Dojo Objective Harness (DOH) at the dojo project website.
- Explore the range
of JavaScript unit-testing frameworks.
Discuss
- Connect with other developerWorks users
through developerWorks community while exploring the developer-driven
blogs, forums, groups, and wikis.
- Help build the Real world open source group in the developerWorks
community.

Hazem Saleh has seven years of experience in Java Enterprise and open source technologies. He is an Apache committer and the author of "The Definitive Guide to Apache MyFaces and Facelets" (Apress). He is also an author of many technical articles, a developerWorks contributing author, and a technical speaker at both local and international conferences. He has spoken at JavaOne and the IBM Regional Technical Exchange. Hazem is a Web 2.0 Subject Matter Expert and is a Staff Software Engineer for IBM Egypt.



