Efficient JavaScript unit testing

Automate JavaScript code testing across browsers

JavaScript code that runs on one browser does not necessarily mean it will work on others. Without unit testing this code, organizations pay money for testing and re-testing web applications when deciding to upgrade or support new browsers. In this article, learn how efficient unit testing of your JavaScript can reduce testing costs and make it easier for you to support more browsers.

Share:

Hazem Saleh, Staff Software Engineer, IBM  

Photo of Hazem SalehHazem 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.



27 September 2011

Also available in Chinese Russian Japanese Spanish

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.


JsTestDriver

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 JsTestDriver architecture

jsTestDriver is open source

jsTestDriver is an open source project under the Apache 2.0 license, hosted on Google Code, a project repository similar to SourceForge. Developers can create and manage public projects at this repository as long as they use one of the licenses approved by the Open Source Initiative.

Many other JavaScript unit testing tools are available. See the resources section below for references to other tools, such as the Dojo Objective Harness (DOH).

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:

  1. JavaScript files — source files and test files, and
  2. 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.


Write the unit testing code

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
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.


Conclusion

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.


Download

DescriptionNameSize
The source codesimple.zip3.35MB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source, Java technology
ArticleID=760736
ArticleTitle=Efficient JavaScript unit testing
publish-date=09272011