Skip to main content

In pursuit of code quality: Unit testing Ajax applications

Testing asynchronous applications gets easier with the GWT

Andrew Glover (aglover@stelligent.com), President, Stelligent Incorporated
Andrew Glover is president of Stelligent Incorporated, which helps companies address software quality with effective developer testing strategies and continuous integration techniques that enable teams to monitor code quality early and often. Check out Andy's blog for a list of his published work.

Summary:  You might get a thrill out of writing Ajax applications, but unit testing them is surely painful. In this article, Andrew Glover takes on the downside of Ajax (one of them, anyway), which is the inherent challenge of unit testing asynchronous Web applications. Fortunately, he finds it easier than expected to tame this particular code quality dragon, with the help of the Google Web Toolkit.

View more content in this series

Date:  24 Jul 2007
Level:  Introductory
Activity:  26666 views

Ajax is undoubtedly one of the biggest buzz words to hit the Web development streets in recent memory -- the proliferation of Ajax-related tools, frameworks, books, and Web sites is evidence that this technology is here to stay. Besides, Ajax applications are pretty slick, aren't they? Yet, as anyone who has developed an Ajax application can testify, testing Ajax isn't exactly easy. In fact, the emergence of Ajax has essentially invalidated a host of test frameworks and tools that weren't designed to test asynchronous Web applications!

Interestingly, the developers of one particular Ajax-enabling framework noticed this limitation and did something quite novel: they built in testability. What's more, because this framework facilitates creating Ajax applications with Java™ code rather than JavaScript, they built on the shoulders of giants, so to speak, and took advantage of what's arguably the standard testing framework for the Java platform: JUnit.

The framework I'm talking about is, of course, the immensely popular Google Web Toolkit, also known as the GWT. In this article, I'll show you how the GWT actually leverages its Java compatibility to make Ajax applications every bit as testable as their synchronous counterparts.

Improve your code quality

Don't miss Andrew Glover's Code quality discussion forum for assistance with code metrics, test frameworks, and writing quality-focused code.

JUnit and GWTTestCase

Because GWT-related Ajax applications are written using Java code, they're perfectly suited to developer testing with JUnit. In fact, the GWT development team created a helper class dubbed GWTTestCase that extends from JUnit's 3.8.1 TestCase. This base class adds functionality for testing GWT code as well as handling some of the plumbing to get a GWT component up and running.

The Google Web Toolkit

The Google Web Toolkit was released to the Java Web development community with great fanfare and was received with equally great excitement. The GWT provides an innovative way to leverage Java code to design, build, and deploy Ajax-enabled Web applications. Rather than having to learn JavaScript and spend hours detangling browser-specific issues, Java Web developers can jump right in to the dynamic, information-rich Web application designs associated with Ajax.

One thing to keep in mind is that GWTTestCase isn't meant to test UI-related code -- it's meant to facilitate testing asynchronous aspects that can be triggered by UI interactions. Mistaking the purpose of GWTTestCase has led many developers new to GWT to become frustrated because they expect it to mimic user interactions easily, and they find that it doesn't.

There basically are two aspects to an Ajax component: its look and feel and its functionality, which of course is designed to be asynchronous. Figure 1 demonstrates a simple Ajax component that emulates a Web form. Because the component is Ajax enabled, the form submit is executed asynchronously (that is, without the page reload associated with traditional form submits).


Figure 1. A simple Ajax-enabled Web form
A simple Ajax-enabled Web form.

If you enter in a valid word and click the Submit button of this component, a message is sent to a server requesting the word's definition. The definition is returned asynchronously through a callback and is accordingly inserted into the Web page, as shown in Figure 2:


Figure 2. After clicking the Submit button, a response is displayed
After clicking the Submit button, a response is displayed.

Functional versus integration testing

Testing the interaction shown in Figure 2 can be broken out into a number of different scenarios, but two stand out. From a functional standpoint, you'd probably want to write a test to fill in the form value, click the Submit button, and then verify the presence of the definition under the form. A second option would be an integration test, which would allow you to validate the asynchronous functionality of the client-side code. The GWT's GWTTestCase was designed for just this sort of test.

The thing to remember is that user interaction testing isn't possible with the GWTTestCase test case environment. When designing and building GWT applications, you must think about testing the code without relying on user interactions. This sort of thinking requires a decoupling of interaction code from business logic, which (as you already know) is a best practice to begin with!

For example, take another look at the Ajax application shown in Figures 1 and 2. This application is composed of four logical components: a TextBox for entering a desired word, a Button for clicking, and two Labels (one for the TextBox and the other where the definition is displayed). An initial approach to the actual GWT module could look like what you see in Listing 1, but how would you test this code?


Listing 1. A valid GWT application, but how to test it?
                
public class DefaultModule implements EntryPoint {

 public void onModuleLoad() {
   Button button = new Button("Submit");
   TextBox box = new TextBox();
   Label output = new Label();
   Label label = new Label("Word: ");

   HorizontalPanel inputPanel = new HorizontalPanel();
   inputPanel.setStyleName("input-panel");
   inputPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
   inputPanel.add(label);
   inputPanel.add(box);

   button.addClickListener(new ClickListener() {
    public void onClick(Widget sender) {
      String word = box.getText();
      WordServiceAsync instance = WordService.Util.getInstance();
       try {
         instance.getDefinition(word, new AsyncCallback() {
           
           public void onFailure(Throwable error) {
             Window.alert("Error occurred:" + error.toString());
           }

           public void onSuccess(Object retValue) {
             output.setText(retValue.toString());
           }
		  });
        }catch(Exception e) {
          e.printStackTrace();
		}
	}
   });

   inputPanel.add(button);
   inputPanel.setCellVerticalAlignment(button,
     HasVerticalAlignment.ALIGN_BOTTOM);

   RootPanel.get("slot1").add(inputPanel);
   RootPanel.get("slot2").add(output);
   }
}

While in working order, the code in Listing 1 suffers from a major deficit: it can't be tested a la JUnit and GWT's GWTTestCase. In fact, if I tried to write tests for this code, they would technically run but wouldn't ever logically work. Think about it for a second: how would you verify anything about this code? The only public method available to test returns void, so how will you verify its correct functionality?

If I want to validate this code in a white box manner, I have to separate UI-specific code from business code, which requires some refactoring. In essence, it means moving the meat of the code in Listing 1 into isolated methods that can be easily tested. That isn't as simple as it sounds, however. The component's hook is obviously through the onModuleLoad() method, but if I want to force its behavior, I might have to manipulate some UI components.


Decoupling UI code from business logic

My first step is to create accessor methods for each UI component, shown in Listing 2. That way, I can grab them if I need to.


Listing 2. Adding accessor methods to UI components to make them accessible
                

public class WordModule implements EntryPoint {

  private Label label;
  private Button button;
  private TextBox textBox;
  private Label outputLabel;
  
  protected Button getButton() {
   if (this.button == null) {
    this.button = new Button("Submit");
   }
   return this.button;
  }

  protected Label getLabel() {
   if (this.label == null) {
    this.label = new Label("Word: ");
   }
   return this.label;
  }

  protected Label getOutputLabel() {
   if (this.outputLabel == null) {
    this.outputLabel = new Label();
   }
   return this.outputLabel;
  }

  protected TextBox getTextBox() {
   if (this.textBox == null) {
    this.textBox = new TextBox();
    this.textBox.setVisibleLength(20);
   }
   return this.textBox;
  }
}

I've now enabled programmatic access to all of my UI-related components (assuming that all the classes that require access are in the same package). Only time will tell if I actually need to use one of these accessors for verification purposes. I'm hoping to limit my use of the accessors because, as I've already mentioned, the GWT isn't meant to be used for interaction testing. In this case, I'm not really trying to test whether a button instance has been clicked, but rather that the GWT module will call my server-side code for a given word and that the server side will return a valid definition. I do this by pushing (no pun intended!) the onModuleLoad() method's definition-retrieving logic into a testable method, as shown in Listing 3:


Listing 3. The refactored onModuleLoad method defers to more testable methods
                
public void onModuleLoad() {
  HorizontalPanel inputPanel = new HorizontalPanel();
  inputPanel.setStyleName("disco-input-panel");
  inputPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);

  Label lbl = this.getLabel();
  inputPanel.add(lbl);

  TextBox txBox = this.getTextBox();
  inputPanel.add(txBox);

  Button btn = this.getButton();

  btn.addClickListener(new ClickListener() {
   public void onClick(Widget sender) {
     submitWord();
   }
  });

  inputPanel.add(btn);
  inputPanel.setCellVerticalAlignment(btn,
      HasVerticalAlignment.ALIGN_BOTTOM);

  if(RootPanel.get("input-container") != null) {
   RootPanel.get("input-container").add(inputPanel);
  }

  Label output = this.getOutputLabel();
  if(RootPanel.get("output-container") != null) {
   RootPanel.get("output-container").add(output);
  }
}

As you can see in Listing 3, I've essentially deferred onModuleLoad()'s definition-retrieving logic to the submitWord method, which is defined below in Listing 4:


Listing 4. The meat of my Ajax application!
                
protected void submitWord() {
  String word = this.getTextBox().getText().trim();
  this.getDefinition(word);
}

protected void getDefinition(String word) {
 WordServiceAsync instance = WordService.Util.getInstance();
 try {
   instance.getDefinition(word, new AsyncCallback() {

   public void onFailure(Throwable error) {
    Window.alert("Error occurred:" + error.toString());
   }

   public void onSuccess(Object retValue) {
    getOutputLabel().setText(retValue.toString());
   }
  });
 }catch(Exception e) {
   e.printStackTrace();
 }
}

The submitWord() method defers to another method dubbed getDefinition(), which I can test using JUnit. The getDefinition() method is logically separated from UI-specific code (for the most part) and can be called without concern for a button click. On the other hand, state issues associated with asynchronous applications and the semantic rules of the Java language dictate that I can't completely avoid UI-related interactivity in my tests. Looking closely at the code in Listing 4, you can see that the getDefinition() method, which invokes an asynchronous callback, does manipulate some UI components -- namely an alert window for errors and a Label instance.

I can still verify the application's functionality by obtaining a handle to the output Label instance and asserting that its text is the definition for a given word. When testing via GWTTestCase, I'm best off not attempting to manually force a state change on a component, but rather letting GWT do it. For example, in Listing 4, I want to verify that the correct definition is returned and placed into an output Label for a given word. I don't actually have to manipulate a UI component to set the word, though; I can just call the getDefinition method directly and then assert that the Label has the corresponding definition.

Now that I've written my GWT application with testing in mind, I actually have to write the test for it, which means setting up the GWT's GWTTestCase.


Setting up GWTTestCase

GWTTestCase requires you to play by some rules if you want to benefit from its testing magic. Luckily, the rules are simple:

  • All implementing test classes must reside in the same package as the GWT module you're attempting to test.

  • When running tests, you must pass in at least one VM argument to help specify which GWT mode (hosted or Web) to run the test in.

  • You must implement the getModuleName() method, which returns a String representation of your XML module file.

Last, because an Ajax application that communicates with a server-side entity is asynchronous by nature, the GWT offers an additional Timer class that facilitates delaying JUnit long enough for asynchronous behavior to complete before an associated assertion.

Implementing getModuleName and the Timer class

As I mentioned earlier, the brunt of my testing effort will be focused on the getDefinition() method (shown in Listing 4). As you can see from the code, the test is logically simple: pass in a word (such as pugnacious) and then verify that the corresponding Label has the correct definition as its text. Simple, right? Don't forget, though, that the getDefinition() method has some asynchronicity associated with it through the AsyncCallback object.

The GWTTestCase class is abstract because its getModuleName() method is declared as such; consequently, when you extend the class, you'll be required to implement getModuleName() (unless you are creating your own base abstract class for a framework). The module name is essentially the package structure where your GWT XML file resides minus the file extensions. For example, in my case, I have an XML file named WordModule.gwt.xml, which resides in a directory structure like this: com/acme/gwt. Consequently, the logical name of the module is com.acme.gwt.WordModule, which should remind you of the Java platform's normal packaging scheme.

Now that I've got a module name, I can begin defining a test case as in Listing 5:


Listing 5. You must implement the getModuleName method and provide a valid name
                
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.user.client.Timer;

public class WordModuleTest extends GWTTestCase {

 public String getModuleName() {
   return "com.acme.gwt.WordModule";
 }
}

So far, so good, but I haven't tested anything yet! Because my Ajax application uses the AsyncCallback object, I'll have to force JUnit to delay running to completion when I call the getDefinition() method via my test case; otherwise, the test will fail due to no response. This is where GWT's Timer class comes in handy. Timer allows me to override getDefinition()'s run method and finish the test case's logic within Timer instead. (The test case will be run in separate thread, effectively blocking JUnit from completing the overall test case.)

For example, with my test, I'll first call the getDefinition() method and then provide an implementation of a Timer's run() method. The run() method obtains the text of the output Label instance and verifies that the proper definition is there. After I've defined a Timer instance, I then need to schedule it to launch while also forcing JUnit to hang around for the Timer instance to complete. If this sounds complicated, don't worry, it's quite easy in practice. In fact, you can see the entire scenario in Listing 6:


Listing 6. Testing with GWT is a cinch
                
public void testDefinitionValue() throws Exception {
 WordModule module = new WordModule();
 module.getDefinition("pugnacious");
 Timer timer = new Timer() {
   public void run() {
    String value = module.getOutputLabel().getText();
	String control = "inclined to quarrel or fight readily;...";   
	assertEquals("should be " + control, control, value);
    finishTest();
   }
  };
  timer.schedule(200);
  delayTestFinish(500);
}

As you can see, the Timer's run() method is where I actually verify the functionality of my Ajax application and its use of a remote procedure call. Note that the last step in my run method is to call the finishTest() method, which signals that everything went as expected and JUnit should run as normal without further blocking. In practice, you may find you'll need to hone your delay times further depending on how long the asynchronous behavior requires to complete. But the point of testing GWT applications through JUnit is that you can do it without having to deploy the fully functional Web application to test it. As a result, you can test your GWT applications earlier and hopefully more often.


Running a GWT test

Functional testing with the GWT

A simple Ajax application like the one demonstrated here could be verified from a functional standpoint using a framework like Selenium, which drives a browser to simulate actual user behavior. To run a functional test using Selenium, you would have to deploy the fully functional Web application, however.

As I mentioned earlier, if you'd like to actually run your GWT JUnit tests, you'll have to do a number of little things to configure a running environment. For example, to run my tests via Ant's junit task, I have to ensure some files are in my classpath and provide an argument to the underlying JVM. Specifically, when invoking the junit task, I need to ensure the directory (or directories) that hosts my source files (including tests) is in the classpath, and I need to tell the GWT which mode to run in. I tend to use the hosted mode, which means I use the www-test flag, shown in Listing 7:


Listing 7. Running GWT tests with Ant
                
<junit dir="./" failureproperty="test.failure" printSummary="yes" 
	 fork="true" haltonerror="true">
	
 <jvmarg value="-Dgwt.args=-out www-test" />
 
 <sysproperty key="basedir" value="." />
 <formatter type="xml" />
 <formatter usefile="false" type="plain" />
 <classpath>
  <path refid="gwt-classpath" />
  <pathelement path="build/classes" />
  <pathelement path="src" />
  <pathelement path="test" />
 </classpath>
 <batchtest todir="${testreportdir}">
  <fileset dir="test">
   <include name="**/**Test.java" />
  </fileset>
 </batchtest>
</junit>

Running the GWT tests then becomes a matter of invoking my build. Also note that GWT tests are fairly lightweight, so I can run tests frequently, or even continuously, as I would in a Continuous Integration environment.


In conclusion

In the GWT test case demonstrated in this article, you've seen the first steps to verifying that an Ajax application runs as it should. You could continue testing my example GWT application, for instance by testing a few more bounds cases, but I think my point has been made: Testing Ajax applications is easier than you would think, provided they've been written using a framework that incorporates testing features.

The key to testing GWT applications well (as is true of most applications) is to design the application with testing in mind. Also note that GWTTestCase isn't meant to be used for interaction testing. You cannot use GWTTestCase to simulate users directly. You can, however, use it to verify user interactions in an indirect manner, as I've demonstrated in this article.


Resources

About the author

Andrew Glover

Andrew Glover is president of Stelligent Incorporated, which helps companies address software quality with effective developer testing strategies and continuous integration techniques that enable teams to monitor code quality early and often. Check out Andy's blog for a list of his published work.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Web development
ArticleID=242226
ArticleTitle=In pursuit of code quality: Unit testing Ajax applications
publish-date=07242007
author1-email=aglover@stelligent.com
author1-email-cc=

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers