Test driven development for GWT UI code

by Rob van MarisMarch 9, 2008

Is a test driven development (TDD) approach possible when creating a GUI using GWT? This post is about how to apply MVC (Model-View-Controller) to organize GWT UI code, with these objectives:

  1. a clear separation of the controller logic from presentation and model
  2. full testability of the controller logic independent of the (browser or hosted mode) GWT environment.

We will see how this can be realized by adding a few simple classes to the standard set of GWT widgets, and applying an MVC flavor described by Martin Fowler as Supervising Controller.
The sample code shows how tests can provide a programmatic alternative for tests you would otherwise have to perform manually by interacting with the UI of the deployed application.

The sample application

To set the stage, let’s introduce a small sample GWT application. It’s a currency converter that converts between Euro’s and Dutch Guilders (HFL). As you might guess, I’m Dutch, and HFL is the currency we used to have in the Netherlands before the Euro was introduced in 2002. Though HFL have gone out of circulation ever since, many Dutch people still convert amounts mentally between Euro and HFL, and this application does just that. The image below shows what it looks like in the hosted mode.

The sample application

Enter the HFL amount in the upper field, and the lower field will show the amount converted to Euro, or vice-versa. The interesting thing is that the fields can each be set both by the user typing in an amount, as well as by the application responding to input. After entering an amount in a field, it is processed as soon as the field loses focus, i.e. the user clicks or tabs away from the field.

Testability

Once deployed in hosted mode or a browser, manual testing of this application is straightforward:

  1. Enter a value in one of the fields
  2. Move focus away from the field
  3. Verify that the other field is updated as expected

What about automated unittesting? Ideally we would write unittests, using JUnit, that perform the same steps. Our first option is to extend GWTTestCase. But that is not really what we’re looking for. Firstly, automated unittests based on GWTTestCase are cumbersome to set up. Secondly, and even worse, these tests start the entire application in hosted mode in the background, which makes them terribly slow. For me Test Driven Development (TDD) is the way to go, which means I run my tests frequently and they have to be fast.
In TDD-speak, the GWTTestCase approach is really in-container testing. This is something we want to avoid, for the same reason that we want to avoid server-side tests that require the entire application to be started in an application server. What I’m looking for is lightweight unittests that can be run outside of the GWT container, i.e. outside of the (hosted mode or browser) GWT environment.

Classic MVC

Designing for testability requires partitioning the application into a set of components that provide a clear separation of responsibilities. Having to face this issue upfront is one of the key benefits of TDD.
We’ll be guided by the Model-View-Controller (MVC) pattern, which has been around for decades. The diagram below shows its components and how they interact.
Basic MVC
Basically, we have a View which lays out a couple of widgets, a Controller which reacts to user actions, and a Model which contains the state of (part of) the application. As a result of user actions, the Controller receives events and in response processes application logic and updates the Model. The View (in this case its widgets) receives notification when the Model has been updated, and updates itself accordingly.

Now let’s see what this looks like in code. First, the model:

public class ConverterModel extends BaseModel {
    private final static float EUR_HFL = 2.20371F;

    private float amountHfl = 0;
    private float amountEur = 0;

    public float getAmountHfl() {
        return amountHfl;
    }

    public float getAmountEur() {
        return amountEur;
    }

    public void updateAmountHfl(float amountHfl) {
        this.amountHfl = amountHfl;
        this.amountEur = amountHfl/EUR_HFL;
        fireModelChangedEvent();
    }

    public void updateAmountEur(float amountEur) {
        this.amountEur = amountEur;
        this.amountHfl = amountEur * EUR_HFL;
        fireModelChangedEvent();
    }
}

Nothing fancy here. The model holds the amount in both HFL and EUR. It has methods for both amounts to be updated, which set the amount to the provided value and calculate the other amount using the currency conversion ration. Both update methods finally call the method fireModelChangedEvent() in the BaseModel superclass, which is listed below.

public class BaseModel {
    private List listeners = new ArrayList();

    public void addModelChangeListener(ModelChangeListener listener) {
        listeners.add(listener);
    }

    public void removeModelChangeListener(ModelChangeListener listener) {
        listeners.remove(listener);
    }

    public void fireModelChangedEvent() {
        for (Iterator iListeners = listeners.iterator(); iListeners.hasNext();) {
            ((ModelChangeListener) iListeners.next()).onModelChanged(this);
        }
    }
}

The BaseModel class just provides convenience methods to register ModelChangeListener instances and send them modelChanged events. ModelChangeListener, listed below, provides a simple interface to be notified of changes to the model.

public interface ModelChangeListener {
    public void onModelChanged(BaseModel model);
}

Now it’s time to introduce the View:

public class ConverterView extends Composite implements ModelChangeListener {

    private TextBoxWidget input1 = new TextBoxWidget();
    private TextBoxWidget input2 = new TextBoxWidget();

    public ConverterView() {
        // Layout
        VerticalPanel panel = new VerticalPanel();

        HorizontalPanel row1 = new HorizontalPanel();
        Label label1 = new Label("HFL");
        row1.add(input1);
        row1.add(label1);
        panel.add(row1);

        HorizontalPanel row2 = new HorizontalPanel();
        Label label2 = new Label("EUR");
        row2.add(input2);
        row2.add(label2);
        panel.add(row2);

        initWidget(panel);

        // Model
        ConverterModel model = new ConverterModel();
        model.addModelChangeListener(this);

        // Controller
        ConverterController controller = new ConverterController(model);
        controller.registerHflInput(input1);
        controller.registerEurInput(input2);

        // Initialize view to initial state of model
        model.fireModelChangedEvent();
    }

    public void onModelChanged(BaseModel model) {
        ConverterModel converterModel = (ConverterModel) model;
        input1.setText(Float.toString(converterModel.getAmountHfl()));
        input2.setText(Float.toString(converterModel.getAmountEur()));
    }
}

In case you wonder what TextBoxWidget is, it’s a subclass or the GWT TextBox widget. I’ll come back to that later.
The View lays out the widget in panels and creates and wires the Model and Controller. It registers the inputfields with the Controller, so the Controller can subscribe to their events. And it subscribes itself as a ModelChangeListener to the model. Consequently, when the model changes the View’s onModelChange() method is called, allowing the View to update the inputfields.

Finally, here’s the code for the Controller.

public class ConverterController {

    private ConverterModel model;

    public ConverterController(ConverterModel model) {
        this.model = model;
    }

    void registerHflInput(final TextInput hflInput) {
        hflInput.addFocusListener(new FocusListenerAdapter() {
            public void onLostFocus(Widget sender) {
                try {
                    float amountHfl = Float.parseFloat(hflInput.getText());
                    model.updateAmountHfl(amountHfl);
                } catch (NumberFormatException e) {
                    // Ignore
                }
            }
        });
    }

    void registerEurInput(final TextInput eurInput) {
        eurInput.addFocusListener(new FocusListenerAdapter() {
            public void onLostFocus(Widget sender) {
                try {
                    float amountEur = Float.parseFloat(eurInput.getText());
                    model.updateAmountEur(amountEur);
                } catch (NumberFormatException e) {
                    // Ignore
                }
            }
        });
    }
}

The Controller creates anonymous inner classes that handle lostFocus events from the inputfields, and subscribes these to the inputfields. Instead of using anonymous inner classes I could have the Controller register itself and add a method to handle the events, but as it is I think it expresses my intentions best. For all intents and purposes, the inner classes can be considered integral part of the object that contains them, so it’s still valid to say that the Controller handles the events. The event handling code attempts to convert the input text to a float that is used to update the model.
In case you’re wondering what TextInput is, bear with me until later. For now let’s pretend that the actual TextBox widgets are passed in as parameter to the register methods.

Now let’s create a test!

import junit.framework.TestCase;
import nl.robvanmaris.gwtqs.client.input.TextInputMock;
import nl.robvanmaris.gwtqs.client.mvc.ModelChangeListener;
import static org.easymock.EasyMock.*;

public class ConverterControllerTest extends TestCase {
    private ConverterController instance;

    private ConverterModel model;
    private TextInputMock hflTextInput = new TextInputMock();
    private TextInputMock eurTextInput = new TextInputMock();
    private ModelChangeListener viewMock = createMock(ModelChangeListener.class);

    protected void setUp() throws Exception {
        model = new ConverterModel();
        model.addModelChangeListener(viewMock);
        instance = new ConverterController(model);
        instance.registerHflInput(hflTextInput);
        instance.registerEurInput(eurTextInput);
    }

    /**
     * Pass condition: changing HFL amount in input results in updated model.
     */
    public void testChangeAmountHfl() {
        viewMock.onModelChanged(model);

        replay(viewMock);
        hflTextInput.setText("2.20371");
        hflTextInput.fireLostFocus();
        verify(viewMock);

        assertEquals(1.0f, model.getAmountEur());
    }

    /**
     * Pass condition: changing EUR amount in input results in updated model.
     */
    public void testChangeAmountEur() {
        viewMock.onModelChanged(model);

        replay(viewMock);
        eurTextInput.setText("1.0");
        eurTextInput.fireLostFocus();
        verify(viewMock);

        assertEquals(2.20371f, model.getAmountHfl());
    }
}

This time I’ve included the import statements, so you can see I’m using JUnit and EasyMock. In the test setup the Model, View and Controller components are wired together exactly like in the View code you saw before. The tests simulate what happens when the user enters a value in one of the inputfields: it sets the value of the input field and fires a lostFocus event. Then it asserts that the model is updated as expected. In addition, the actual View has been mocked using EasyMock. The View mock is used to verify that the model receives an onModelChanged event.

So now we have a unittest for the Controller, but can we run it outside of the GWT container? There is one more thing we need to take care of.

Mocking out the widgets

Remember our goal was to be able to test our code outside of the GWT environment. That means we should be able to run our unittests without having to instantiate GWT widgets. This is because widget instantiation ultimately relies on a GWT.create() call, which can only succeed in a GWT (hosted mode or browser) environment. For our Controller to be testable outside the GWT container, it should not rely on widgets, but on a common interface that is can be implemented by both the actual widgets and lightweight replacements we can use in our test. So what interface should that be?
For the Controller to do its work and and be testable, there’s only two things it needs to know about the text input widgets:

  1. the widget contains a string that can be set
  2. the widget can fire focus events

The GWT API provides interfaces for both of these capabilities: HasText and SourcesFocusEvents. All the Controller needs to know about a text input field is that it implements both of these interfaces. So I’ve introduced a new interface that combines them:

public interface TextInput extends SourcesFocusEvents, HasText {
}

In order to provide the actual widgets to the Controller, I’ve extended them to implement this TextInput interface:

public class TextBoxWidget extends TextBox implements TextInput {
}

Extending the TextBox widgets was easy, as the required methods are already present. Next to this, I had to create a mock TextInput implementation for use in the test:

public class TextInputMock implements TextInput {

    private List focusListeners = new ArrayList();

    private String text;

    public void fireLostFocus() {
        for (Iterator iFocusListeners = focusListeners.iterator();
        iFocusListeners.hasNext();) {
            ((FocusListener) iFocusListeners.next()).onLostFocus(null);
        }
    }

    public void addFocusListener(FocusListener listener) {
        focusListeners.add(listener);
    }

    public void removeFocusListener(FocusListener listener) {
        focusListeners.remove(listener);
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}

This mock implementation provides just the minimum functionality required for it to mimick an actual text input field in the test. It holds a string value that can be set, and FocusListeners can subscribe to it and be notified of lostFocus events.

Classic MVC limitations

So this is as far as the application of classic MVC gets us. We have achieved a clear separation of responsibilities between the Model, View and Controller and we can test out-of-container how the Controller responds to user input and updates the Model.
That’s great, because it covers most of the logic in the UI. In our sample application the Controller logic is relatively simple. In a more advanced application the Controller logic would be more complex, e.g. involve RPC calls to the server, and our test can be extended to deal with that as well (more on that later). But there is still a part missing. If you were to test the application manually, you would verify that the input fields are updated to the expected value in response to user actions. While our unittests verify that the Model is updated and the View is notified of the change to the Model, the one thing we didn’t verify is the final state of the View (i.e the input fields) itself. This is because updating the View is a responsibility of the View itself, and we had to mock the View since the original View requires a GWT environment.
To overcome this final obstacle, we have to remove the responsibility of updating the View from the View and put some other component in charge of that.

Supervising Controller to the rescue

Like so often, the problem at hand is not particular to GWT, and it has been solved before. Enter MVC with a twist, described by Martin Fowler as Supervising Controller. The idea is to move logic to the components that are easiest to test.
So let’s make updating the View another responsibility of the (now Supervising) Controller. The diagram below shows how the components interact.
MVC with Supervising Controller
The important difference with classic MVC is this: the View no longer subscribes as a ModelChangeListener to the Model in order to update itself when the Model changes. Instead the (Supervising) Controller subscribes to the Model and updates the View.

Let’s see how this affects the code. Firstly, here’s the revised View.

public class ConverterView extends Composite {

    private TextBoxWidget input1 = new TextBoxWidget();
    private TextBoxWidget input2 = new TextBoxWidget();

    public ConverterView() {
        // Layout
        VerticalPanel panel = new VerticalPanel();

        HorizontalPanel row1 = new HorizontalPanel();
        Label label1 = new Label("HFL");
        row1.add(input1);
        row1.add(label1);
        panel.add(row1);

        HorizontalPanel row2 = new HorizontalPanel();
        Label label2 = new Label("EUR");
        row2.add(input2);
        row2.add(label2);
        panel.add(row2);

        initWidget(panel);

        // Model
        ConverterModel model = new ConverterModel();

        // Controller
        ConverterController controller = new ConverterController(model);
        controller.registerHflInput(input1);
        controller.registerEurInput(input2);

        // Initialize view to initial state of model
        model.fireModelChangedEvent();
    }
}

Compared to the previous version, the View has become simpler. The lines dealing with subscribing to the Model and handling its events have been removed. That logic has moved to the Controller.

public class ConverterController {

    private ConverterModel converterModel;

    public ConverterController(ConverterModel converterModel) {
        this.converterModel = converterModel;
    }

    void registerHflInput(final TextInput hflInput) {
        hflInput.addFocusListener(new FocusListenerAdapter() {
            public void onLostFocus(Widget sender) {
                try {
                    float amountHfl = Float.parseFloat(hflInput.getText());
                    converterModel.updateAmountHfl(amountHfl);
                } catch (NumberFormatException e) {
                    // Ignore
                }
            }
        });
        converterModel.addModelChangeListener(new ModelChangeListener() {
            public void onModelChanged(BaseModel model) {
                hflInput.setText(Float.toString(converterModel.getAmountHfl()));
            }
        });
    }

    void registerEurInput(final TextInput eurInput) {
        eurInput.addFocusListener(new FocusListenerAdapter() {
            public void onLostFocus(Widget sender) {
                try {
                    float amountEur = Float.parseFloat(eurInput.getText());
                    converterModel.updateAmountEur(amountEur);
                } catch (NumberFormatException e) {
                    // Ignore
                }
            }
        });
        converterModel.addModelChangeListener(new ModelChangeListener() {
            public void onModelChanged(BaseModel model) {
                eurInput.setText(Float.toString(converterModel.getAmountEur()));
            }
        });
    }
}

The two methods that register the inputs now also handle the events from the Model.

Finally, here’s the revised test.

public class ConverterControllerTest extends TestCase {
    private ConverterController instance;

    private ConverterModel model;
    private TextInputMock hflTextInput = new TextInputMock();
    private TextInputMock eurTextInput = new TextInputMock();

    protected void setUp() throws Exception {
        model  = new ConverterModel();
        instance = new ConverterController(model);
        instance.registerHflInput(hflTextInput);
        instance.registerEurInput(eurTextInput);
        model.fireModelChangedEvent();
    }

    /**
     * Pass condition: changing HFL amount in input results in updated model and view.
     */
    public void testChangeAmountHfl() {
        hflTextInput.setText("2.20371");
        hflTextInput.fireLostFocus();

        assertEquals(1.0f, model.getAmountEur());
        assertEquals("1.0", eurTextInput.getText());
    }

    /**
     * Pass condition: changing EUR amount in input results in updated model and view.
     */
    public void testChangeAmountEur() {
        eurTextInput.setText("1.0");
        eurTextInput.fireLostFocus();

        assertEquals(2.20371f, model.getAmountHfl());
        assertEquals("2.20371", hflTextInput.getText());
    }
}

Surprisingly, the test code has become even simpler. Firstly, we no longer need a (mocked) View instance in the tests. Just mocks for the inputfields are all we need. Secondly, we can explicitly test the endstate of the inputfields, while in the previous version all we could access was the endstate of the Model. Best of all, the remaining lines of code of both test methods express in a very concise way how an actual user interaction is mimicked:

  1. enter a value in the input
  2. move input focus away from the input
  3. assert that the value in the other input is updated to the correct value

Note that in addition to this the unittests verify the resulting value in the model. In our sample application there’s no immediate need for this, but it’s a good idea to do this in general. There may be other components accessing the model as well, so we better make sure it is in the expected state.

Finally, consider what we did not test: the remaining logic in the View lays out the widget, instantiates the remaining components and registers the widgets with the Controller.
Not testing this is perfectly acceptable for me. The layout logic is one of those things you preferably test by visually inspecting the result in a browser. The logic of instantiating components and registering widgets is basically just configuration logic, expressed programmatically. There is very little logic here, it is simple, changes rarely and is easy to troubleshoot.

Advanced usage

In a real-world application the logic in the Controller and Model is usually more complex, but this doesn’t pose any new problems to our approach. For instance RPC calls may be involved, but mocking these is straightforward (more on this in my next blog post). When the Model contains complex logic, it may be preferable to test it in isolation, and mock it in the unittests for the Controller.

Conclusion

The sample code demonstrates that the logic for a GWT UI can be tested out of container. The necessary ingredients are:

  • Introducing interfaces that allow the widgets to be replaced by mocks at test time.
  • Moving all logic to be tested into a Supervising Controller and a Model.

Further reading

Credit where credit is due. This work was partly inspired by ideas presented earlier by Iwein Fuld from Springsource. We worked together on a project, and he posted a blog about it previously.
Martin Fowler’s article about GUI architectures provides an invaluable overview of UI design patterns that have been successful in the past.