Enabling Test Driven Development in GWT client code

Iwein Fuld

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.

Update 2008-10: Sources of the sample are outdated. The general advice still holds.

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.

GWT flavored MVC pattern

Similar Posts

Share this Post
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • DZone
  • LinkedIn
  • Slashdot
  • Technorati
  • TwitThis
 

19 responses


  1. Very valuable stuff here. I have been hesitating on GWT because of the difficulty in testing. Thanks for such an excellent post.


  2. Seems like the post contains a formatting error that fucks up the rest of the page, at least it does on IE7.


  3. [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).


  4. Can 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] ————————————————————————


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


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


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


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


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


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


  11. Hi,
    I wonder if you can help me with a MVC question that I have…
    I am using GWT and I had to put my Model package inside the GWT client package.
    The problem is that I am using Hibernate Anottations to persist my data, and the anottations stays inside the Model Classes.
    So, I am forced to import Hibernate libraries inside to my client package.
    I didn't like that…

    Do you have a suggestion for a way that I can work together with hibernate anottations, MVC and GWT ?


  12. This is a problem that people traditionally solve with Dto's. With GWT 1.5 it is also possible to call non GWT json services (or any webservice for that matter), so in that way you could completely decouple client and server.

    If you don't want to use a conversion layer to transform your domain objects to Dto's AND you don't want to completely decouple client from server. You're stuck with what you have now.

    There are people arguing that orm annotations don't belong on your domain objects anyway, and the problem you're facing is an argument for that. On the other hand it can be very convenient, but then you can't use the same objects on the client cleanly.

    The cleanest solution would be to completely split client and server, the most pragmatic (and ugly) one is to have the hibernate dependency on the client. Using xml instead of annotations is the middle ground. Let me know how it worked out for you, it'd be interesting to hear your experiences.


  13. Good post, nice to see people test driving GWT. I agree testing logic using unit tests is a good idea, however I'm not so sure about testing UI interactions using this style of unit test is all that useful. I believe you point this out above anyway (you mention you'd rather check the registration happened correctly).

    For example, imagine an application where a drop down dynamically hides some buttons. You could still fire events against those buttons, even though they don't appear on the screen – hence the unit test would still pass. If you use acceptance testing for the UI interactions (ie. via Selenium), the test would fail.

    My preference is to separate any logic that exists on the view into a separate class and unit test that. For example we've recently coded a chart widget that used various coordinate systems and axis scales – all of which could be driven by unit tests. However the click interactions with the chart had to be driven by acceptance tests.

    I'd be interested to see how strong you find the tests over time as well – i.e. if you are refactoring the view/controller, how easy it is to deal with broken tests.

    Good to see other people trying new stuff with GWT though – and I'll try and keep up to date with your blog.


  14. First I should make clear that unit testing in any form is no replacement for (automated) acceptance testing.

    The second thing is the "logic in view" point. I'd aim to keep the logic in views to an absolute minimum. There can be multiple controllers that keep application state and UI state for example. As long as you don't put it in the view I do not have a very strong opinion on where the controller logic should go exactly. Arguably there is no guarantee that someController.hideSomeViewElement() actually hides the view if you haven't tested that, so that is where UI testing comes in.

    If you're using mocks for the view elements (remember that you cannot mock the actual classes, so you have to extract an interface), you can make sure that the view altering methods are actually called, so you do not have to test every scenario. Also you can be confident that refactoring doesn't break the UI behavior even if you're not doing the acceptance test.

    If you want more in depth examples you could go look for Rob van Maris' blog. (see trackbacks)


  15. Many thanks for this crucial advice. I was starting to plug GWT into my Spring/JPA applications and was getting concerned about the testing. It is important to break the habit of creating Entry Point classes and get into the habit of creating Controllers, especially when application is refactored into many UI modules.

    You can possibly name the zip archive to mvc-demo.tgz.zip, so it can be uploaded.
    The "tgz" in the name will let folks know this is a "tgz" archive. You can possibly put an instruction to "rename" the archive.


  16. Possibly an interesting find … I am working with GWT 1.5.2 and TWO tests fails in NoteEditorControllerTest. From what I have found, the Mock creation

    private NoteServiceAsync noteServiceMock = createMock(NoteServiceAsync.class);

    is invoking GWT.create() automagically and failing as GWT.create() can only be called from within a GWTTestCase. I tried extending the NoteEditorControllerTest from GWTTestCase with all the setup changes, but then all tests started failing – that could be a setup issue.

    To cut a long story short, can someone please try this with GWT 1.5.2?

    I also had to modify the POM slightly to include the windows-platform jars and libraries – Minor change mostly.

    com.google.gwt
    gwt-dev
    ${gwtVersion}
    ${platform}-libs
    zip
    provided

    com.google.gwt
    gwt-dev
    ${gwtVersion}
    ${platform}
    provided


  17. I removed the link to the code. It is outdated. I still stand by my original advice though. If I have some time I will try to provide a more up to date version.


  18. For those interested, the original maven plugin has been released in the central maven repository.
    net.bankras
    mvn-gwt-plugin
    1.0.4


  19. I understand the problems with GWTTestCase but something is bothering me : if you don't use GWTTestCase, you test your client-side java code whereas this code will never be executed as such in your real application (instead, the generated javascript code is executed).
    So you would write some unit tests in pure JUnit AND integration tests with GWTTestCase ?

    By the way, Ext GWT provides a nice MVC framework for GWT.

4 trackbacks

Leave a Reply