Google Code offered in: English - Español - 日本語 - 한국어 - Português - Pусский - 中文(简体) - 中文(繁體)
Creating a battery of good unit test cases is an important part of ensuring the quality of your application over its lifecycle. To aid developers with their testing efforts, GWT provides integration with the popular JUnit unit testing framework. GWT allows JUnit test cases to run in either hosted mode or web mode, and also provides a benchmarking facility to help you understand the performance characteristics of the components of your application.
This section will describe how to create and run a set of unit test cases for your GWT project. In order to use this facility, you must have the JUnit library installed on your system.
GWT includes a special GWTTestCase base class that provides JUnit integration. Running a compiled GWTTestCase subclass under JUnit launches an invisible instance of the GWT hosted mode browser which serves to emulate your application behavior during test execution.
GWTTestCase is derived from JUnit's TestCase. The typical way to setup a JUnit test case class is to have it extend TestCase, and then run the it with the JUnit TestRunner. TestCase uses reflection to discover the test methods defined in your derived class. It is convention to begin the name of all test methods with the prefix test.
GWT includes a junitCreator tool that can generate a starter test case for you, plus scripts for testing in both hosted mode and web mode.
The following is an example of creating a test class using the junitCreator that implements GWTTestCase. The example assumes that you have already created a module named com.example.foo.Foo:
~/Foo> junitCreator -junit /opt/eclipse/plugins/org.junit_3.8.1/junit.jar
-module com.example.foo.Foo
-eclipse Foo com.example.foo.client.FooTest
Created directory test/com/example/foo/test
Created file test/com/example/foo/client/FooTest.java
Created file FooTest-hosted.launch
Created file FooTest-web.launch
Created file FooTest-hosted
Created file FooTest-web
After creating the skeleton of the test case and the launch file, add your testing logic to the generated FooTest.java class file. Make sure to compile the class using javac or your favorite IDE.
Running FooTest-hosted tests the methods in test/com/example/foo/client/FooTest.java as Java bytecode in a JVM. FooTest-web tests as compiled JavaScript. The launch configurations do the same thing in Eclipse.
If you prefer not to use the junitCreator, you may create a test case suite by hand by following the instructions below:
First of all, you will need a valid GWT module to host your test case class. Usually, you do not need to create a new module XML file - you can just use the one you have already created to develop your GWT module. If you do not already have a com.example.foo.Foo module, create it as follows:
<module> <!-- Module com.example.foo.Foo --> <!-- Standard inherit. --> <inherits name='com.google.gwt.user.User'/> <!-- implicitly includes com.example.foo.client package --> <!-- OPTIONAL STUFF FOLLOWS --> <!-- It's okay for your module to declare an entry point. --> <!-- This gets ignored when running under JUnit. --> <entry-point class='com.example.foo.FooModule'/> <!-- You can also test remote services during a JUnit run. --> <servlet path='/foo' class='com.example.foo.server.FooServiceImpl'/> </module>
Tip: You do not need to create a separate module for every test case. In the example above, any test cases in com.example.foo.client (or any subpackage) can share the com.example.foo.Foo module.
You need to be able to access the code that is to be unit tested through some public method. In this example, the module Foo has a validation function that tests a string and returns true or false:
/**
* A sample routine that runs in the module that you want to run a
* unit test on. Accepts the strings "foo" and "bar" in any case,
* and the string "Baz" as an exact match. All other strings fail.
*
* @param input A string to validate
* @return true if the string passes validation.
*/
public boolean exampleValidator (String input) {
if (input.toLowerCase().equals("foo")){
return true;
} else if (input.toLowerCase().equals("bar")) {
return true;
} else if (input.equals("Baz")) {
return true;
}
return false;
}
Here is an example implementation com.example.foo.client.FooTest test case that invokes the validation function with different input strings to make sure the implementation matches the specified behavior in the comment:
import com.google.gwt.junit.client.GWTTestCase;
import com.example.foo.client.Foo;
public class FooTest extends GWTTestCase {
/**
* Specifies a module to use when running this test case. The returned
* module must cause the source for this class to be included.
*
* @see com.google.gwt.junit.client.GWTTestCase#getModuleName()
*/
public String getModuleName() {
return "com.example.foo.Foo";
}
/**
* Test the method 'exampleValidator()' in module 'Foo'
*/
public void testExampleValidator() {
Foo myModule = new Foo();
assertTrue(myModule.exampleValidator("foo"));
assertTrue(myModule.exampleValidator("Foo"));
assertTrue(myModule.exampleValidator("FOO"));
assertTrue(myModule.exampleValidator("fOo"));
assertTrue(myModule.exampleValidator("bar"));
assertTrue(myModule.exampleValidator("Baz"));
assertFalse(myModule.exampleValidator("fooo"));
assertFalse(myModule.exampleValidator("oo"));
assertFalse(myModule.exampleValidator("baz"));
}
Now, there are several ways to invoke your test, depending on how you created the unit test and your environment.
If you created your unit tests with junitCreator, execute your test with the command FooTest-hosted:
$ ./FooTest-hosted .. Time: 12.679 OK (1 test)
If you did not use the junitCreator tool, you can create your own script similar to what the tool would have created. Here is the script that the tool created to run tests in hosted mode for Linux:
#!/bin/sh
APPDIR=`dirname $0`;
java -Dgwt.args="-out www-test" -cp "$APPDIR/src:$APPDIR/test:$APPDIR/bin:
/opt/eclipse/plugins/org.junit4_4.3.1/junit.jar:
/home/user/gwt/gwt-linux-1.4.60/gwt-user.jar:
/home/user/gwt/gwt-linux-1.4.60/gwt-dev-linux.jar"
junit.textui.TestRunner com.example.client.FooTest "$@";
And here is an example script to launch the test on Windows:
@java -Dgwt.args="-out www-test" -cp "%~dp0\src;%~dp0\test;%~dp0\bin;
C:\eclipse\plugins\org.junit_3.8.2.v200706111738\junit.jar;
C:/gwt/gwt-windows-1.4.60/gwt-user.jar;
C:/gwt/gwt-windows-1.4.60/gwt-dev-windows.jar"
junit.textui.TestRunner com.example.client.FooTest %*
Some IDEs have direct support for the JUnit testing framework. When run with the -eclipse argument, the junitCreator tool optionally creates a .launch configuration file that you can invoke from the Open Run Dialog... menu option in Eclipse. The configuration appears under the JUnit heading.
When using the junitCreator tool, several command-line scripts are created to launch your tests in either hosted mode or web mode. Make sure you test in both modes - although rare, there are some differences between Java and JavaScript that could cause your code to produce different results when deployed.
If you do not use the junitCreator tool and instead decide to run the JUnit TestRunner from command line, you must add some additional arguments get your unit tests running. By default, tests run in hosted mode are run as normal Java bytecode in a JVM. Overriding this default behavior requires passing arguments to the GWT shell. Arguments cannot be passed directly through the command-line, because normal command-line arguments go directly to the JUnit runner. Instead, define the system property gwt.args to pass arguments to GWT. For example, to run tests in web mode (that is, run the tests afer they have been compiled into JavaScript), declare -Dgwt.args="-web" as a JVM argument when invoking JUnit. To get a full list of supported options, declare -Dgwt.args="-help" (instead of running the test, help is printed to the console).
Manual mode tests allow you to run unit tests manually on any browser. This mode was introduced in the GWT 1.5 release. In this mode, the JUnitShell main class runs as usual on a specified GWT module, but instead of running the test immediately, it prints out a URL and waits for a browser to connect. You can manually cut and paste this URL into the browser of your choice, and the unit tests will run in that browser.
Manual mode test scripts are not generated by the tool, but you can easily create one by copying the <module-name>-web script and replacing the -web with the -manual argument in the -Dgwt.args part of the script.
When developing a large project, a good practice is to integrate the running of your test cases with your regular build process. When you build manually, such as using ant from the command line or using your desktop IDE, this is as simple as just adding the invocation of JUnit into your regular build process. When building in a server environment, there are a few extra considerations.
As mentioned before, when you run GWTTestCase tests, there is an invisible browser running. However, even though the browser is invisible, the test case classes still require the GUI libraries to run. If you need to run unit test cases on a headless server (usually as a part of an automated build), make sure the same GUI libraries that would be used to run GWT hosted mode are installed on the server. On Linux, you can run the X Virtual Framebuffer (Xvfb) to be a target for the invisible browser before launching your tests.
Also, consider organizing your tests into GWTTestSuite classes to get the best performance from your unit tests.
The tests described above are intended to assist with testing client side code. The test case wrapper GWTTestCase will launch either a hosted mode session or a web browser to test the generated JavaScript. On the other hand, server side code runs as native Java in a JVM without being translated to JavaScript, so it is not appropriate to run tests of server side code using 'GWTTestCase' as the base class for your tests. Instead, use JUnit's TestCase and other related classes directly when writing tests for your application's server side code.
GWT's JUnit integration provides special support for testing functionality that cannot be executed in straight-line code. For example, you might want to make an RPC call to a server and then validate the response. However, in a normal JUnit test run, the test stops as soon as the test method returns control to the caller, and GWT does not support multiple threads or blocking. To support this use case, GWTTestCase has extended the TestCase API. The two key methods are GWTTestCase.delayTestFinish(int) and GWTTestCase.finishTest(). Calling delayTestFinish() during a test method's execution puts that test in asynchronous mode, which means the test will not finish when the test method returns control to the caller. Instead, a delay period begins, which lasts the amount of time specified in the call to delayTestFinish(). During the delay period, the test system will wait for one of three things to happen:
The normal use pattern is to setup an event in the test method and call delayTestFinish() with a timeout significantly longer than the event is expected to take. The event handler validates the event and then calls finishTest().
public void testTimer() {
// Setup an asynchronous event handler.
Timer timer = new Timer() {
public void run() {
// do some validation logic
// tell the test system the test is now done
finishTest();
}
};
// Set a delay period significantly longer than the
// event is expected to take.
delayTestFinish(500);
// Schedule the event and return control to the test system.
timer.schedule(100);
}
The recommended pattern is to test one asynchronous event per test method. If you need to test multiple events in the same method, here are a couple of techniques:
The GWTTestSuite mechanism has the overhead of having to start a hosted mode shell and servlet or compile your code. You can potentially increase the speed of your unit test runs by organizing your tests into suites of test cases.
Combining test cases together into a single GWTTestSuite reduces overhead if multiple GWTTestCase classes share the same module (that is, they return the same value from getModuleName().) The GWTTestSuite class re-orders the test cases so that all cases that share a module are run back to back.
Creating a suite is simple if you have already defined individual JUnit TestCases or GWTTestCases. Here is an example:
public class MapsTestSuite extends GWTTestSuite {
public static Test suite() {
TestSuite suite = new TestSuite("Test for a Maps Application");
suite.addTestSuite(MapTest.class);
suite.addTestSuite(EventTest.class);
suite.addTestSuite(CopyTest.class);
return suite;
}
}
The three test cases MapTest, EventTest, and CopyTest can now all run in the same instance of JUnitShell.
java -Xmx256M -cp "./src:./test:./bin:./junit.jar:/gwt/gwt-user.jar:/gwt/gwt-dev-linux.jar:/gwt/gwt-maps.jar" junit.textui.TestRunner com.example.MapsTestSuite
When using a test method in a JUnit TestCase, any objects your test creates and leaves a reference to will remain active. This could interfere with future test methods. In the GWT 1.5 release, you can override two new methods to prepare for and/or clean up after each test method.
In GWT versions prior to GWT 1.5, you can override the standard JUnit setUp() and tearDown() methods. However, you are not allowed to use JSNI methods or code that depends on deferred binding (which includes almost all of the UI library).
The following example shows how to defensively cleanup the DOM before the next test run using gwtSetUp(). It skips over <iframe> and <script> tags so that the GWT test infrastructure is not accidentally removed.
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
private static native String getNodeName(Element elem) /*-{
return (elem.nodeName || "").toLowerCase();
}-*/;
/**
* Removes all elements in the body, except scripts and iframes.
*/
public void gwtSetUp () {
Element bodyElem = RootPanel.getBodyElement();
List<Element> toRemove = new ArrayList<Element>();
for (int i = 0, n = DOM.getChildCount(bodyElem); i < n; ++i) {
Element elem = DOM.getChild(bodyElem, i);
String nodeName = getNodeName(elem);
if (!"script".equals(nodeName) && !"iframe".equals(nodeName)) {
toRemove.add(elem);
}
}
for (int i = 0, n = toRemove.size(); i < n; ++i) {
DOM.removeChild(bodyElem, toRemove.get(i));
}
}
GWT's JUnit integration provides special support for creating and reporting on benchmarks. Specifically, GWT has introduced a new Benchmark class which provides built-in facilities for common benchmarking needs. To take advantage of benchmarking support, take the following steps:
To use the benchmarking classes, start by using junitCreator to create a skeleton JUnit class and launch configuration.
> junitCreator -junit ~/tools/lib/junit/junit-3.8.1.jar -module com.example.foo.Foo -eclipse FooBenchmark com.example.foo.test.FooBenchmark Created directory /home/user/workspace/Foo/test/com/example/client/test Created file /home/user/workspace/Foo/test/com/example/foo/client/FooBenchmark.java Created file /home/user/workspace/Foo/FooBenchmark-hosted.launch Created file /home/user/workspace/Foo/FooBenchmark-web.launch Created file /home/user/workspace/Foo/FooBenchmark-hosted Created file /home/user/workspace/Foo/FooBenchmark-web
Now, edit FooBenchmark.java:
A Benchmark class differs from a JUnit class in that the test methods will be called repeatedly taking different values each time. The Benchmark class is very flexible in this regard - you can specify as many parameters as you like, whereas you must leave a method definition with no arguments to satisfy the JUnit specification.
For each parameter, you will need to provide a single constant containing values to iterate over during the test. GWT provides some built in classes to help define such ranges such as IntRange. For example, this represents the values 512, 1024, 2048, 4096, ... :
final IntRange baseRange = new IntRange(512, Integer.MAX_VALUE,
Operator.MULTIPLY, 2);
You can create any range of values you like with whatever object values returned, as long as you specify an instance class of Iterable in the annotation for the function parameter.
public void testSomething( @RangeField("baseRange") Integer size ) {...}
In this case, size is a parameter to the test function, and the range of values are defined by the Iterable instance named baseRange.
Here is a full example of a simple Benchmark subclass that compares the execution time of adding an element to a Vector to adding an element to an ArrayList:
package com.example.foo.client;
import com.google.gwt.benchmarks.client.Benchmark;
import com.google.gwt.benchmarks.client.IntRange;
import com.google.gwt.benchmarks.client.Operator;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
/**
* GWT benchmarks must extend Benchmark.
*/
public class FooBenchmark extends Benchmark {
final IntRange baseRange = new IntRange(512, Integer.MAX_VALUE,
Operator.MULTIPLY, 2);
private List testVector = new Vector();
private List testList = new ArrayList();
/**
* Must refer to a valid module that sources this class.
*/
public String getModuleName() {
return "com.example.foo.Foo";
}
/**
* JUnit requires a method that takes no arguments. It is not used for
* Benchmarking.
*/
public void testSimpleVector() {
}
/**
* Test adding an element to a vector
*/
public void testSimpleVector(@RangeField("baseRange") Integer size)
{
int len = size.intValue();
testVector.clear();
for (int i=0 ; i<len; i++) {
testVector.add(size);
}
}
public void testSimpleList() {
}
/**
* Test adding an element to a List
*/
public void testSimpleList(@RangeField("baseRange") Integer size)
{
int len = size.intValue();
testList.clear();
for (int i=0 ; i<len; i++) {
testList.add(size);
}
}
}
Now, run the script FooBenchmark-hosted that was created by junitCreator to run the benchmark. The script will run your code in hosted mode and create a file named report-*.xml. You can view this report with the benchmarkViewer command from command line:
For more advanced usage information, see the Benchmark class documentation.
The Lightweight metrics system is a good tool to find key areas where latency may be noticeable to your end users. Some of the advantages of the system are:
The Debug Panel for GWT, developed by fellow Google engineers and GWT contributors, is also using the Lightweight metrics system and provides an easy way to collect metrics as well as test your GWT application. You can read more details about the features of the tool in this blog post.
The Lightweight Metrics system is composed of sets of events that you're interested in tracking and a global collector method that is responsible for evaluating these events and reporting metrics.
For example, when loading a GWT application, the steps involved in the process consist of bootstrapping the application, loading external references, and starting up the GWT module. Each of these steps further break down into dowloading the bootstrap script, selecting the right permutation of your application to load, fetching the permutation, and so on. This is illustrated in the Lightweight Metrics design doc (see GWT Startup Process diagram). Each of the smaller steps, like selecting the correct permutation of the application to load, can be represented as events you would like to measure in the overall application load time. The events themselves are standard JSON objects that contain the following information:
{
moduleName : <Module Name>,
subSystem : <Subsystem name>,
evtGroup : <Event Group>,
millis : <Current Time In Millis>,
type : <Event Type>
}
The moduleName is the name of your GWT module (refer to GWT documentation for more information on GWT modules). The subSystem refers to the specific component that is emitting these events in your GWT application (for example, the GWT RPC subsystem). The evtGroup is analogous to a grouping of related events that can be assumed to follow a serial order. The millis field contains the timestamp when the event was emitted, and the type field indicates the actual method or step that was run and emitted the event. Each (moduleName, subSystem, evtGroup, type) tuple can be interpreted as a checkpoint in an event group.
In the GWT Startup Process, the event for selecting a permutation might look something like:
{
moduleName : 'Showcase',
subSystem : 'startup',
evtGroup : 'bootstrap',
millis : new Date().getTime();
type : 'selectingPermutation'
}
The global collector function, named __gwtStatsEvent(), is called whenever you want to report an event to measure. It can either be defined directly in your host HTML page, or injected dynamically. As long as it gets defined before your GWT bootstrap script is executed, it will be correctly setup to report event metrics. From within the global collection function implementation you can decide how you want to display the metrics collected for the given processes you measured.
Here's an example of what the __gwtStatsEvent() function might look like if you wanted to log all the events you have timed in your GWT application:
<head>
<title>Hello</title>
<script language='javascript'>
function eventToString(event) {
// return some string representation of this event
return event.evtGroup + " | " + event.moduleName + " | " + event.subSystem + " | " + event.type + " | " + event.millis;
}
window.__gwtStatsEvent = function(event) {
var loggingDiv = document.getElementById('log');
if (!loggingDiv) {
// Our logging div is not yet attached to the DOM
// Initialize a temporary buffer if needed
this.buffer = (this.buffer) ? this.buffer : [];
// log data here
this.buffer.push(event);
} else {
if (this.buffer) {
// We have some data that was reported before the div was connected
for (var i = 0; i < buffer.length; i++) {
// print it all to the div
var bufferedEvent = buffer[i];
var logline = document.createElement("div");
logline.id = "logline";
logline.innerHTML = eventToString(bufferedEvent);
loggingDiv.appendChild(logline);
}
this.buffer = null;
}
// log the current event to the div
var logline = document.createElement("div");
logline.id = "logline";
logline.innerHTML = eventToString(event);
loggingDiv.appendChild(logline);
}
}
</script>
</head>
<body>
<div id="log"><h3>Statistics for Events Logged</h3></div>
<script type="text/javascript" language="javascript" src="hello/hello.nocache.js"></script>
<iframe src="javascript:''" id="__gwt_historyFrame" style="position:absolute;width:0;height:0;border:0"></iframe>
</body>
The GWT bootstrap sequence and the GWT RPC mechanism are already instrumented. You can start collecting information about these events simply by defining the global collector function. To see the system in action, add the global collector function in the snippet above to your host HTML page.
You can use the Lightweight Metrics system to measure important events that are specific to your own application. For example, suppose you have a potentially expensive method call somewhere in your entry point onModuleLoad() called createWidget(). Create the following wrapper method over the global stats collector function to measure the time it takes for that method to execute:
public class StatsEventLogger {
public static native void logEvent(String moduleName, String subSystem, String eventGroup, String millis, String type) /*-{
$wnd.__gwtStatsEvent({
'moduleName' : moduleName,
'subSystem' : subSystem,
'evtGroup' : eventGroup,
'millis' : millis,
'type' : type
});
}-*/;
}
Next, add calls before and after the code you want to profile in the createWidget() method, as shown below:
public FlexTable createWidget() {
FlexTable listings = new FlexTable();
String startTime = String.valueOf(new Date().getTime());
StatsEventLogger.logEvent(GWT.getModuleName(), "listings", "loadListings", startTime, "begin");
loadListings(listings, range);
String endTime = String.valueOf(new Date().getTime());
StatsEventLogger.logEvent(GWT.getModuleName(), "listings", "loadListings", endTime, "end");
return listings;
}
The initial load times as well as RPC execution times for multiple modules can be measured at the same time since they each will make the same call to the __gwtStatsEvent() global collector function and pass in their module name as part of the event information reported. This makes it easier to evaluate varying functional implementations of a method and compare their performance. You can also compare groups of events across various modules by grouping them by the evtGroup field allowing you to compare larger interaction implementations directly against each other.