Enabling Test Driven Development in GWT client code

In the past months I've been working with various clients on projects using Google Web Toolkit [GWT]. I like GWT primarily because of the Java to javascript compiler. This is the key to the door letting mere mortal Java developers create RIA's without having to learn a new language.
I've allways been a fan of test driven development, and to my disappointment at first sight it looked like TDD and GWT were not going to play together.
Testing GWT code is a bit problematic. The core of the problem is that GWT code is compiled to javascript before it is run. In many cases a GWT.create() statement is used to hook into the dynamic binding mechanism. When executed in normal Java environment this statement causes an exception.
Even when you use a mocking library like EasyMock to mock out the culprit you are still likely to run into the problem if it is triggered from a constructor for example. Creating interfaces for all widgets is just too much overhead, and even when you do that you will not be able to test the specific class that has the GWT.create() in it.
GWT provides a solution for this problem in GWTTestCase, but this class has quite a few problems of its own. Just to name a few:
- it starts hosted mode from within the testrunner (making it slow)
- it needs to run on GWT compiled code (making it slow)
- it waits for the async service with a timeout (making it slow)
It provides a decent mechanism to do integration testing on your client code, but for unit testing it is too bloated.
When you do test driven development you need to be able to write tests with at least the following properties:
- They run from the IDE immediately (no switching, no waiting)
- They run fast (the upper bound in the order of magnitude of 10 seconds)
- They can test units in isolation (mocking or stubbing out the dependencies)
All these requirements are about quickly and easily switching between test mode and development mode. Because if that is possible you can keep your entire focus on solving the problem. From personal experience I can say that this is a sure way to get into flow, but your mileage may vary. In any case, if these basic requirements are not met, you will get distracted during development and that eliminates the advantage of upfront testing, which is the essence of TDD.
So what do we do to keep our code from turning into spaghetti by lack of testing, but keeping those slow GWTTestCases to a minimum?
MVC to the rescue
MVC is a veteran pattern. There have been many applications of it, some of which have almost nothing in common with the original MVC pattern except for the name, but the general direction I would like to point in is best summarized by Martin Fowler under the name Humble View.
My even shorter summary is this: views are usually very hard to test, therefore they should know and do as little as possible. In GWT a view is synonymous to a Widget. Now there is a slight catch here: anything that runs on the client is owned by a Widget. Most of the GWT code has pretty much all the logic in widgets, so most developers using GWT extend a widget and just add some more logic.
My advice is simple: don't go there. Type safety is nice to get IDE support, it does nothing to prevent you from writing bad code. So if you don't unit test, your code you'll end up being a mess.
If you don't go down the path of putting logic in your Views (Widget subclasses) you'll need another place to put it. That's where the Controller comes in. Coming back to the catch I mentioned before, we will need to bootstrap the controller from a Widget or EntryPoint. There is really no way around this, so let's just do it and see how bad it looks:
public class NoteEditor extends Composite {
public NoteEditor() {
//do the dependency injection stuff
NoteModel noteModel = new NoteModel();
NoteEditorController controller =
new NoteEditorController(noteModel, NoteService.App.getInstance(), new AlertCallback());
//...
}
}
As you can see I've done some dependency injection from the code here, because we don't have Spring on the client yet. I say yet, because we could use GWToolbox or Rocket for that later. The service takes a GWT.create() to get bootstrapped, and the AlertCallback is used to decouple the controller from Window for the occasional alert. This is not so bad I'd say, I can sleep easily without writing a test for this code.The trouble is not entirely over, because any element we want to use in the view (buttons, labels etc) needs to be instantiated in the view and then registered with the controller:
controller.registerDetailViewSelector(new DeckPanelSelector(detailView));
detailView.addStyleName("detailPanel");
main.add(detailView, DockPanel.CENTER);
//some buttons at the bottom of the screen
buttonPanel.add((Widget)controller.registerClearButton(new Button("Clear")));
buttonPanel.add((Widget)controller.registerLoadButton(new Button("Load existing Note")));
The controller accepts the buttons under their interface (SourcesClickEvents) which as a bonus allows us to replace the button with some Widget with a different look without having to change our controller. Nothing new here, this is exactly the separation of concerns that MVC is about. To be honest I'd normally prefer to write a test to check that the registration has happened correctly, but this is something that can't be done without GWTTesCase.Now it is time to let our IDE create that controller + methods for us and write the test so we can implement the logic. For example the test for the load button would look something like this:
public void testLoadButtonPressed_success() throws Exception {
final Foo expectedFoo = new Foo("expected");
fooServiceMock.loadFoo(isA(String.class), isA(AsyncCallback.class));
expectLastCall().andAnswer(new IAnswer() {
public Object answer() throws Throwable {
((AsyncCallback) getCurrentArguments()[1]).onSuccess(expectedFoo);
return null;
}
});
fooModelMock.setFoo(expectedFoo);
replay(allMocks);
loadButton.fireClick();
verify(allMocks);
}
And off we go! I've attached a quick sample to show the idea. You can use it as a starting point for experiments.
Sources of the sample can be found here. To play with them:
- unpack
- run mvn jetty:run-exploded
- go to http://localhost:8080/
- optionally cd to ./etc/bin and run gwt-shell.sh for the hosted mode
Other maven targets will work as well of course.
To summarize a quick sketch of the GWT flavoured MVC pattern. But of course you can choose your own flavor, as long as you keep your logic in a testable class.
Modified

David Hainlin says:
Added on February 19th, 2008 at 7:10 amVery valuable stuff here. I have been hesitating on GWT because of the difficulty in testing. Thanks for such an excellent post.
Erwin Vervaet says:
Added on February 19th, 2008 at 2:22 pmSeems like the post contains a formatting error that fucks up the rest of the page, at least it does on IE7.
Iwein Fuld (blog author) says:
Added on February 20th, 2008 at 3:16 am[quote comment="98907"]Seems like the post contains a formatting error that fucks up the rest of the page, at least it does on IE7.[/quote]
Thanks for the heads up. I'd missed a tag, should be fixed now (i didn't check in IE).
Georges says:
Added on February 20th, 2008 at 11:05 amCan not run it… where do I find the repos holding the missing dependency?
$ mvn jetty:run-exploded
[INFO] Scanning for projects…
[INFO] Searching repository for plugin with prefix: 'jetty'.
Downloading: http://repo1.maven.org/maven2/nl/jteam/maven-gwt-plugin/1.0.3/maven-gwt-plugin-1.0.3.pom
Downloading: http://repo1.maven.org/maven2/nl/jteam/maven-gwt-plugin/1.0.3/maven-gwt-plugin-1.0.3.pom
[INFO] ————————————————————————
[ERROR] BUILD ERROR
[INFO] ————————————————————————
[INFO] Failed to resolve artifact.
GroupId: nl.jteam
ArtifactId: maven-gwt-plugin
Version: 1.0.3
Reason: Unable to download the artifact from any repository
nl.jteam:maven-gwt-plugin:pom:1.0.3
from the specified remote repositories:
central (http://repo1.maven.org/maven2)
[INFO] ————————————————————————
[INFO] For more information, run Maven with the -e switch
[INFO] ————————————————————————
[INFO] Total time: 1 second
[INFO] Finished at: Wed Feb 20 17:04:26 CET 2008
[INFO] Final Memory: 2M/4M
[INFO] ————————————————————————
Iwein Fuld (blog author) says:
Added on February 20th, 2008 at 11:55 amThanks Georges for pointing out this problem.
There was some config missing from the pom. I've uploaded a working version now (tested with a cleared local repo). You could give it another try if you like.
If you keep having problems with this gwt compiler you can also use the original one from google code or the xi8ix variant.
Glenn Mason says:
Added on February 21st, 2008 at 9:15 pmHi Iwein,
Great post… we are currently wrestling with GWT testing on a project we are currently working on. This post is a big help.
However, the zip archive seems like it may be corrupted. Winzip keeps telling me it is not a valid archive. I'd love the be able to check out your example.
Many thanks,
Glenn
Iwein Fuld (blog author) says:
Added on February 22nd, 2008 at 3:08 am[quote post="280"]Winzip keeps telling me it is not a valid archive.[/quote]
Use "tar -xvfz mvc-demo.zip" from a terminal and you'll be fine. I couldn't name the file *.tgz because of restrictions to uploads in this blog but most operating systems don't make a problem out of that.
If you have to unpack it on windows you could try another zip utility. I've heard that the native windows one and Winzip are not all that great. 7zip is often recommended in this context.
I hope this helps.
Iwein Fuld says:
Added on March 14th, 2008 at 4:07 amIf you're looking for a practical application of the idea's in this post you should have a look at http://robvanmaris.jteam.nl/2008/03/09/test-driven-development-for-gwt-ui-code/. Rob van Maris has applied the techniques in this post to a strict Supervising Controller.
Edson Yanaga says:
Added on April 2nd, 2008 at 2:50 pmHi Iwein,
Do you know if the nl.jteam:maven-gwt-plugin is opensource? If yes, where can I get the source for it? I would like to develop with gwt using the project you provided as an example, but of course I have some fears of using a closed source plugin that I don't know if it will be available after a year or not.
Iwein Fuld says:
Added on April 7th, 2008 at 4:09 amThere is a bit of a problem with well supported open source alternatives for mvn plugins. The jteam one works pretty well, but I don't know about support for it. If you download the xi8ix one you can get the source and adapt it to accept memory arguments. It's not ideal, but on the other hand its not very complicated either.