Subscribe in a reader

10 October 2009

Introduction to MVP Unit Testing - Part Two

In part one we began to look at MVP unit testing techniques using a Vehicle Wizard application and EasyMock. This article continues from where we left off by exploring more tests of presenter logic and then moves on to testing of dispatch actions (commands), finally looking at testing the view code itself.

Resources

All test code and resources are given in part one.

MVP Test Scenarios

During the development of the wizard I frequently came across the following scenarios in my tests which we'll be covering in this article:

  • Verify the registration of event handlers;
  • Verify that events have been fired;
  • Verify navigation outcomes;
  • Verify that data is correctly bound to the model;
  • Testing gwt-dispatch actions (commands);
  • Testing validation logic in the view.

Verify the Registration of Event Handlers

Here we want to verify that an event handler for an event type was correctly registered with the event bus. This situation raised an interesting behavioural aspect with EasyMock. The following code represents a typical handler registration:


eventBus.addHandler(WizardActionResponseEvent.getType(), new ActionResponseHandler());

If we want to verify that an event handler has been correctly registered then we cannot use something like the following:

expect(eventBus.addHandler(WizardActionResponseEvent.getType(), new ActionResponseHandler())).andReturn(null);

The reason is simple and it's down to EasyMock's default argument matching logic relying on equals(). Neither GwtEvent.Type or ActionResponseHandler implement equals. So we're left with the default Object.equals() implementation which compares object references. Okay, our getType() call returns a static reference so this would match okay, but the handler argument is not static and will always fail. It's not practical to define equals() for all our handlers, so we need some other way to compare our arguments. To assist in this situation EasyMock has a built-in argument matcher called EasyMock.isA() which compares an argument based on its type - which sounds perfect for this case. With all this in mind you might be tempted to define your expectation as follows:


expect(eventBus.addHandler(WizardActionResponseEvent.getType(), isA(ActionResponseHandler.class))).andReturn(null);

Unfortunately this will fail with the following error from EasyMock:


java.lang.IllegalStateException: 2 matchers expected, 1 recorded.

So how do we fix this? It turns out that EasyMock has an all or nothing approach to defining argument matchers and by specifying an argument matcher for only the second argument we'd broken the code. The fix is simple enough - specify an argument matcher for the first argument too:


expect(eventBus.addHandler(eq(WizardActionResponseEvent.getType()), isA(ActionResponseHandler.class))).andReturn(null);

Now the test should pass.

Verifying That Events Have Been Fired

A pain point that I experienced when first using EasyMock was verifying that the right events had been placed on the event bus. As with testing handler registration covered in part one, this was down to EasyMock's argument matching. Under gwt-presenter an event is fired using something like the following:


eventBus.fireEvent(new WizardActionRequestEvent<Enum<?>>(getPlace(), action, stepId));

In this case I wanted the argument matcher to also take into account the payload of the event rather than just verifying that "an event had been fired" or even if "an event of the correct class type had been fired". Again, I wanted to avoid implementing equals on the object for the sake of testing. EasyMock supports this situation by allowing you to define custom argument matcher which is kind of similar to using java.util.Comparator in the JDK:


public class WizardActionRequestEventMatcher implements IArgumentMatcher {

private final WizardActionRequestEvent expected;

protected WizardActionRequestEventMatcher(final WizardActionRequestEvent expected) {
this.expected = expected;
}

@Override
public void appendTo(final StringBuffer buf) {
buf.append(getClass().getSimpleName()).append(".matches(").append(expected).append(")");
}

@Override
public boolean matches(final Object arg0) {
if (!(arg0 instanceof WizardActionRequestEvent)) {
return false;
}

final WizardActionRequestEvent actual = (WizardActionRequestEvent) arg0;

final boolean result = /* perform custom comparison here */;

return result;
}
}

The code to make the argument comparisons is implemented in matches and the code for reporting mismatch errors is defined by appendTo. Although a complete implemention is provided above, much of the boiler plate code (reporting errors and testing for object instance type) can be factored into an abstract class leaving little code to write for future tests. Once the argument matcher is written, it may be used to verify if events have fired as follows:


final WizardActionRequestEvent<Enum<?>> expected =
new WizardActionRequestEvent<Enum<?>>(TestWizardPresenter.PLACE, WizardAction.BACK, TestStepEnum.STEP_1);

eventBus.fireEvent(WizardActionRequestEventMatcher.matches(expected));

Verify Navigation Outcomes

Navigation under gwt-presenter is managed by firing the PlaceRequestEvent. Similarly, a PlaceChangedEvent is used to update the history. In the wizard example, argument matchers have been defined PlaceRequestEventMatcher and PlaceChangedEventMatcher which support the verification of these events, for example:


final PlaceRequest request = new PlaceRequest(StartPagePresenter.PLACE);

eventBus.fireEvent(PlaceRequestEventMatcher.matches(new PlaceRequestEvent(request)));

Or verify that a history update with parameters:


final PlaceRequest request =
new PlaceRequest(StartPagePresenter.PLACE)
.with(StartPagePresenter.SHOW_WIZARD_PARAM, StartPagePresenter.SHOW_WIZARD_VALUE);

eventBus.fireEvent(PlaceChangedEventMatcher.matches(new PlaceChangedEvent(request)));

Note: there is currently an issue with gwt-presenter where the PlaceRequest.equals() does not work properly. The version of the gwt-presenter jar supplied with the wizard MVP project has been patched to resolve this issue.

Verify That Data is Correctly Bound to the Model

The following presenter is responsible for operations on the make property of the Car object. The tests want to verify that the right values are applied to the display when we refresh and are assigned back from the display when we wish to commit our changes.


public class CarPresenter extends WidgetPresenter<CarPresenter.Display> {

public interface Display extends WidgetDisplay {
HasValue<String> getMake();
}

private Car car;

public void setCar(final Car car) {
this.car = car;
}

public Car getCar() {
return car;
}

public void commitToModel() {
final String make = display.getMake().getValue();
car.setMake(make);
}

public void bindToModel() {
final String make = car.getMake();
display.getMake().setValue(make);
}

public refreshDisplay() {
bindToModel();
}
}

When using HasValue we need to specify a return value for the HasValue object from the display interface (view) and the call to getValue:


@Test
public void testCommitToModel() {
final Car car = new Car();

presenter.setCar(car);

final String expectedMake = "Volkswagen";

// Set up mock for HasValue and define expected getMake return value
final HasValue<String> mockHasValue = createMock(HasValue.class);

expect(view.getMake()).andReturn(mockHasValue);
expect(mockHasValue.getValue()).andReturn(expectedMake);

replay(view, mockHasValue);

presenter.commitToModel();

verify(view, mockHasValue);

assertEquals(expectedMake, car.getMake());
}

This sort of testing with HasValue can be quite common so the wizard example defines a helper method to assist with defining get and set values, leaving the following respective test methods to check whether a value was applied to the model from the view, or assigned to the view from the model:


@Test
public void testCommitToModel() {
final Car car = new Car();

presenter.setCar(car);

final String expectedMake = "Volkswagen";

final HasValue<String> make = MvpTestHelper.expectHasValueGet(view.getMake(), expectedMake).getValue();

replay(view, make);

presenter.commitToModel();

verify(view, make);

assertEquals(expectedMake, car.getMake());
}


@Test
public void testBindToModel() {
final Car car = new Car();
final String expectedMake = "Volkswagen";

car.setMake(expectedMake);

presenter.setCar(car);

// Set up mock for HasValue and define expected setMake argument value
final HasValue<String> mockHasValue = MvpTestHelper.expectHasValueSet(view.getMake(), expectedMake).getValue();

replay(view, mockHasValue);

presenter.refreshDisplay();

verify(view, mockHasValue);
}

Testing HasWidgets

Although HasWidgets is an interface, it also has a dependency on the Widget class and the mock creation process will result in a call to GWT.create(). This is invalid outside of the GWT client runtime or a GWTTestCase and will throw an UnsupportedOperationException in regular JUnit tests with the message "ERROR: GWT.create() is only usable in client code". To get around this issue you should add the following call to the start of your test's setUp method:


GWTMockUtilities.disarm();

Testing gwt-dispatch Actions (Commands)

Although it's possible to invoke a server call from GWTTestCase, generally a unit test will not actually make a call to a server. However, it still makes sense that we test the following aspects of action (command) execution:

  1. An action was submitted to the dispatcher with the correct type and parameters;
  2. The action callback handler success case;
  3. The action callback handler failure case.

Here is a typical command execution for which we'll write tests:


final String ownerName = display.getOwnerName().getValue();

dispatcher.execute(new ValidateStep1(ownerName), new DisplayCallback<VoidResult>(display) {

@Override
protected void handleFailure(final Throwable cause) {
// Failure logic goes here
}

@Override
protected void handleSuccess(final VoidResult value) {
// Success logic goes here
}
});

In order to test command execution, first we'll need to setup a mock DispatchAsync object that is injected to modules requiring a dispatcher. Add the following to the WizardTestModule:


public class WizardTestModule extends AbstractTestModule {

// ...

@Override
protected void configure() {
// ...

final DispatchAsync dispatchAsync = createStrictMock(DispatchAsync.class);
bind(DispatchAsync.class).toInstance(dispatchAsync);

// ...
}
}

Then in your test's setUp method, get a handle on the mock dispatchAsync using the following:


public class MyPresenterTest {

private DispatchAsync dispatchAsync;
// ...


@Before
public void setUp() throws Exception {
// ...

dispatchAsync = injector.getInstance(DispatchAsync.class);

// ...
}
}

As in previous tests, verifying that the correct action and parameters that were submitted is then a case of using argument matchers. For example, from Step1PresenterTest:


@Test
public void testOnNextRequestLocalValid() {
// ...

final ValidateStep1 expectedCommand = new ValidateStep1(name);

dispatchAsync.execute(ValidateStep1ActionMatcher.matches(expectedCommand), isA(Step1Presenter.ValidationCallback.class));

replay(eventBus, dispatchAsync, view, ...);

// test code here

verify(eventBus, dispatchAsync, view, ...);
}

Where ValidateStep1ActionMatcher will match the action class type and the expected arguments:


public class ValidateStep1ActionMatcher extends AbstractMatcher<ValidateStep1> {

// ...

@Override
protected boolean isMatch(final ValidateStep1 expected, final ValidateStep1 actual) {
final boolean result = expected.getOwnerName().equals(actual.getOwnerName());

return result;
}
}

Testing the dispatcher callback uses similar methods to those used when testing click event handlers. That is by refactoring anonymous inner classes to inner classes we can test the code without having to introduce an intermediate method - plus our test coverage is improved. From the inlined example given above, we refactor the anonymous inner class into ValidationCallback:


final String ownerName = display.getOwnerName().getValue();

dispatcher.execute(new ValidateStep1(ownerName), new ValidationCallback());

// ...

class ValidationCallback extends DisplayCallback<VoidResult> {

public ValidationCallback() {
super(display);
}

@Override
protected void handleFailure(final Throwable cause) {
// Failure logic goes here
}

@Override
protected void handleSuccess(final VoidResult value) {
// Success logic goes here
}
}

We can now easily test the callback handler:


@Test
public void testValidateCallbackFailure1() {
final HasValidation hasValidation = MvpTestHelper.expectHasValidation(view.getValidation()).getValue();

final ValidationException validationExceptionCause = new ValidationException();

hasValidation.processServerErrors(validationExceptionCause);

// The dispatch callback should call the following methods:
view.startProcessing(); // on construction
view.stopProcessing(); // after processing result

replay(eventBus, dispatchAsync, view, hasValidation);

// Construction of the callback will interact with the view, so make
// sure it's in replay mode.
final Step1Presenter.ValidationCallback callback = presenter.new ValidationCallback();

callback.onFailure(validationExceptionCause);

verify(eventBus, dispatchAsync, view, hasValidation);
}

EasyMock Gotchas

When working with EasyMock I came across the following errors enough times to warrant a mention:

NullPointerException may arise from a number of circumstances like forgetting to put mocks into replay:


expect(view.getResult()).andReturn(html);

html.setHTML(expectedHtml);

replay(html);

// This will cause an NPE because the view mock has not been put into replay mode.
view.getResult().setHTML("your value here");

Another situation where you might get an NPE is if your code calls your mock getter more than once. If you forget to configure EasyMock to expect this, then your first call will return the expected value and subsequent calls will return null - possibly resulting in an NPE. When this happens you need to make a judgement as to whether an object should be called multiple times or not, either this is a genuine error or an additional call was omitted when setting expectations.


// the following is missing .times(2)
expect(view.getResult()).andReturn(html);

html.setHTML(expectedHtml);

replay(view, html);

view.getResult().setHTML("your value here");

// This will cause an NPE because the mock object is only configured to return a value for one call.
view.getResult().setHTML("your value here");

If the call count is expected then use the times method to specify expectations. There is a more subtle variant of the above situation where it is also easy to get caught out. For example, imagine when using a mock based on HasValue - we want our mock, say view.getMake(), to return a mock for the HasValue object, but chances are that we'll also be calling getValue and expecting a value from the mock. Both the return expectations for view.getMake() and the mockHasValue mock will need their times expectation setting:


final String expectedMake = "Volkswagen";

// Set up mock for HasValue and define expected getMake return value
final HasValue<String> mockHasValue = createMock(HasValue.class);

// okay
expect(view.getMake()).andReturn(mockHasValue).times(2);

// the following is missing .times(2).
expect(mockHasValue.getValue()).andReturn(expectedMake);

replay(view, mockHasValue);

assertNotNull(view.getMake().getValue());

// Fail.
assertNotNull(view.getMake().getValue());

Another common error is this: IllegalStateException: missing behavior definition for the preceding method call x()

This exception is raised if an expected mock method has a return value, which you've wrapped your call in expect but have not specified a return value with andReturn (or similar). This error can crop up if you're not expecting a method to return a value, e.g. addClickHandler is not a getter, but returns a self reference to support call chaining:


final HasHTML html = createMock(HasHTML.class);

// the following is missing ".andReturn(html)"
expect(view.getResult());
html.setHTML(expectedHtml);

replay(view, html); // exception raised here

Testing Validation Logic

For the large part of the wizard application I was able to make a clear separation between the view and the presenter logic. Adding validation logic seemed to really blur the boundaries. On one hand, validation rules are technically business logic should probably belong in the presenter. But on the other hand, validation operates closely to the view in that failed validation rules will need to update the display and will often present a localisable message (which requires GWT deferred binding). I've been using the gwt-vl library for its support of client and server validation plus handling of localised messages and widget/global level reporting, however this makes the library dependent on UI code. In this case it seemed too much work to force validation logic into the presenter layer, so I opted to leave validation code in the view. However I still wanted to unit test the validation logic and this lead me to defining a subset of tests using GWTTestCase.

View Testing with GWTTestCase

Since these tests run in a different environment and very much slower than our previously created presenter tests, I've found it necessary to separate the tests based on GWTTestCase into their own source folder which I called gwt-test. This is a standard Eclipse source folder, if you're unsure on how to create one, refer to the steps used to create the test source folder in part one.

If you generated your GWT project using the Google Eclipse plugin then you're pretty much ready to go. If not then refer to the GWT dev guide for configuration details.

To configure a test, make your test case extend GWTTestCase. This needs a reference to your client's GWT module file. It's not strictly necessary to define a separate client module for your tests, however I found that I often need to override a few settings to prevent errors in the main compilation process, include specific test utility classes and specify locale values for tests. As in the previous tests, maintain the same package structure as the main app and copy your client's GWT module XML file. If making a separate copy of the module config append "Test" (or whatever you like) to the file name in order to differentiate it from your main module. GWTTestCase uses this to build your GWT code in the test environment. The important parts of the test client config are the <inherits> and <source> sections - the module entry point is ignored.

Before you start testing you should be aware of a limitation of the test environment - there is no parent HTML page for the application so if your views have references to RootPanel then you will most likely experience NullPointerExceptions. See "Avoiding RootPanel" section below for a solution to this.

In your GWT test case class define the location of the module by implementing the abstract method getModuleName (note the lack of filename suffix):


@Override
public String getModuleName() {
return "co.uk.hivedevelopment.wizardmvp.WizardMvpTest";
}

Defining your first view test.


@Test
public void testSimple() {
final Step1View view = new Step1View();

assertNotNull(view.getOwnerName());
}

The test above demonstrates that we can create our view objects directly under GWTTestCase and access a widget. Apart from that, it's not much use except that it does give us a simple starting point from which we can attempt to run our test. If you right click on the test class, you should see an option under "Run As..." for "GWT JUnit Test". Select it and the test will start. Brace yourself - the test might take a while to run depending on your code base size. At least this overhead is per suite execution and not per test or case, so as we add more tests the compilation overhead remains constant.

Running View Tests

As we've just seen, running single test cases under Eclipse is straight forward. To run multiple tests you have a couple of choices. You can either define a GWTTestSuite test suite as follows:


public class GWTTestSuite extends TestSuite {
public static Test suite() {
final Class[] tests = {
// add your GWTTestCase classes here
};

final TestSuite suite = new TestSuite();

for (Class test : tests) {
suite.addTestSuite(test);
}

return suite;
}
}

The other option under Eclipse is that you can define a GWT JUnit Test run target which covers all GWT tests. Here are the minimum steps required to get a working run configuration for a group of GWTTestCase tests:

  1. From the main menu select, Run -> Run Configurations...
  2. Select "GWT JUnit Test" from the left hand project type list, right click on this option and choose "New";
  3. Under name, give the configuration a sensible name;
  4. On the "Main" tab, browse to the project "wizard_mvp". Also select "Run all tests in the selected project, package or source folder" and choose the gwt-test source folder;
  5. Select Apply to save the settings and, if you wish, select Run to execute the tests.

After running the test you will probably see errors relating to code in your presenter class:


[ERROR] Line 35: No source code is available for type com.google.inject.Injector; did you forget to inherit a required module?

Although the unit test passes, this output is annoying. You can get rid of it in one of two ways: 1. tweak the classpath on your test run configuration to exclude the test source folder, or 2) specify source exclusion rules in your test client module. I prefer the latter method since it will apply to all your tests rather than having to tweak each and every separate run configuration (which would be the case in the first approach). You can edit your source entries in your test module as follows:


<source path="client" excludes="**/*PresenterTest.java,**/*HandlerTest.java,**/*TestModule.java" />

This was the motivation behind sticking to a common test naming convention in the presenter tests in part one - it allows easy specification of exclusion rules to prevent GWTTestCase from compiling regular JUnit tests and producing those pesky errors. The result is nice clean output from the test execution. If you still get errors after amending your path then you may need to amend the exclusion list accordingly.

Validation Tests

The view tests in this example focus solely on testing validation logic. The basic approach is to push data into the view, fire validation logic and verify the outcome. The interface between the view and presenter for validation is HasValidation which was created in the wizard MVP example project and provides the isValid method for firing validation. The problem here is that this method returns a Boolean outcome and that's not really enough to test adequately since isValid will often invoke validation rules for multiple fields plus we have no visibility of error messages for assertion. All is not lost however. gwt-vl has the concept of "actions" (not to be confused with gwt-dispatch Actions as part of the Command pattern implementation) which allow the definition of custom behaviour fired on validation failure. gwt-vl actions may be at the field or global level where the custom action will receive all validation results. So by crafting a special global action for our tests we can grab access to enough of the validation result context, such as the failed property name and a localised error message, to carry out more thorough testing.

The global action is really straight forward:


public class AssertValidationAction extends ValidationAction<Object> {

private final Map<String, String> expectedFailures;

public AssertValidationAction(final Map<String, String> expectedFailures) {
this.expectedFailures = expectedFailures;
}

@Override
public void invoke(final ValidationResult result, final Object object) {
for (final ValidationError ve : result.getErrors()) {
final String errorMessage = expectedFailures.remove(ve.propertyName);

assertNotNull(errorMessage);
assertEquals(errorMessage, ve.error);
}

// verify that all expected messages have been checked.
assertTrue(expectedFailures.isEmpty());
}
}

In testing validation we define each of the expected outcomes as a map where the key is the localised property name and the value is the expected localised validation message. Values are then assigned to the view and validation is invoked. When there are validation failures, AssertValidationAction will be invoked with details of the errors, upon which it will chalk off the received errors from the expected map. All being well this process will leave the expectedMap empty, otherwise something went awry and we'll get an assertion failure. A typical validation test might look something like this which verifies a validation failure case:


@Test
public void testValidation_Invalid() {
final Step1View view = new Step1View();

view.getOwnerName().setValue("Jo ");

final HashMap<String, String> expectedFailures = new HashMap<String, String>();

// Verify that all expected messages are produced and that the localised
// property name is correctly resolved.
expectedFailures.put("Owner Name", "Needs a length between '4' and '20'. Was '0'");

view.addGlobalAction(new AssertValidationAction(expectedFailures));

assertFalse(view.getValidation().isValid());
}

@Test
public void testValidation_Valid() {
final Step1View view = new Step1View();

view.getOwnerName().setValue("Chris");

assertTrue(view.getValidation().isValid());
}

The first test will fail at this point as we've yet to specify out test locale. By default, gwt-vl produces German messages and the above tests are written to expect English. If you're using a separate module definition for your tests then you can just add a locale directive to the config:


<set-property name="locale" value="en" />

Now the view validation tests will run ok.

Avoiding RootPanel

Unlike web or hosted mode, GWTTestCase has no underlying HTML document at runtime. Consequently, if your view is accessing elements in the hosting page using RootPanel.get() then you'll get a NullPointerException when running your tests. In the wizard example this problem is avoided by wrapping a call to RootPanel in a content container implementing the WidgetDisplayContainer interface:


public class ApplicationContentContainer extends AbstractContentContainer {

public ApplicationContentContainer() {
super(RootPanel.get("gwt-content-container"));
}

}

public interface WidgetDisplayContainer {

void clear();

void add(WidgetDisplay wd);

void setDisplay(WidgetDisplay wd);

}

public abstract class AbstractContentContainer implements WidgetDisplayContainer {

private final HasWidgets wrapped;

public AbstractContentContainer(final HasWidgets wrapped) {
this.wrapped = wrapped;
}

@Override
public void add(final WidgetDisplay w) {
wrapped.add(w.asWidget());
}

@Override
public void clear() {
wrapped.clear();
}

public void setDisplay(final WidgetDisplay widgetDisplay) {
clear();
add(widgetDisplay);
}

}

Instead of a view adding itself to the DOM, this function is performed by the presenter's revealDisplay method where the ApplicationContentContainer is an injectable component:


@Inject
public ExitPagePresenter(final Display display,
final EventBus eventBus,
final WidgetDisplayContainer displayContainer,
/* ... */) {
super(display, eventBus);

this.displayContainer = displayContainer;
// ...
}


@Override
public void revealDisplay() {
// Adds view to DOM
displayContainer.setDisplay(display);
}

Under the test environment a display container can be provided simply as a mock WidgetDisplayContainer object with which we can validate that the view is wired to the container correctly:


public class ExitPagePresenterTest {

private ExitPagePresenter.Display view;
private WidgetDisplayContainer displayContainer;

@Before
public void setUp() throws Exception {
final Injector injector = Guice.createInjector(new WizardTestModule(StartPagePresenter.class));

eventBus = injector.getInstance(EventBus.class);

displayContainer = injector.getInstance(WidgetDisplayContainer.class);

view = injector.getInstance(StartPagePresenter.Display.class);

// ...
}

@Test
public void testRevealDisplay() {
// revealDisplay adds view to external container
displayContainer.setDisplay(view);

replay(displayContainer);

presenter.revealDisplay();

verify(displayContainer);
}
}

Finally, under the view tests the RootPanel is never accessed and the NPE is prevented.

Running in Hosted Mode and Project Compilation

With the GWTTestCase tests on your classpath, when you run your app from hosted mode or compile the main project you'll get error messages relating to your view tests. To eliminate these you can edit your main module definition by introducing an excludes parameter to ignore view tests:


<source path="client" excludes="**/*ViewTest.java" />

This will prevent errors both in hosted mode execution and in the main project compilation.

Finally, the additional test module introduced for view tests will cause an issue during compilation.

Remove test module from compilation

When compiling your application remove the entry for the test module. Unfortunately, the plugin doesn't currently remember this setting nor does it have an option to ignore it via command arguments so this step must be performed on each compile.

Summary

In this article we continued from where we left off in part one by exploring more detailed unit testing scenarios for presenters and also covered the testing of gwt-dispatch actions (commands) using EasyMock. We also looked at testing aspects of the view by configuring GWTTestCase based tests. That concludes the introduction to unit testing of an MVP application and I hope this gives you enough of a taster for getting your own tests up and running.

Happy Testing

07 October 2009

Introduction to MVP Unit Testing - Part One

In a previous article I gave a basic application based on the Google Web Toolkit (GWT) default starter application to show the configuration of Model View Presenter (MVP), Command, Dependency Injection and Event Bus patterns. One of the main benefits of the MVP pattern is that the decoupling of UI code from business logic makes it easier to unit test, however this was not demonstrated in the previous example. Therefore the aim of this two part article is to introduce some techniques and typical unit testing scenarios for an MVP application using a more substantial example in the form of a Vehicle Details wizard:

Screenshot of the MVP Wizard

The unit testing will cover techniques using both plain JUnit tests with EasyMock for super fast testing of the presenter logic and GWTTestCase unit tests for aspects of the view that really need knowledge of the GWT components during testing.

In part one we will focus on preparing the test environment and getting started with EasyMock. Part two will demonstrate specific MVP testings scenarios with the presenter, view and actions (commands).

Resources

I'll try to keep the code snippets to a minimum. The full source and dependencies are available in the form of an Eclipse 3.5 (+ Google plug-in) project which you can download here.

As well as the Google plug-in, the Wizard MVP example uses the following GWT libraries:

gwt-presenterAn MVP framework;
gwt-dispatchAn implementation of the command pattern;
ginDependency Injection for GWT client-side based on Google's Guice;
gwt-vlA validation library for GWT;
gwt-logA log4j-style logger for GWT.

Note: currently GIN needs to be built from source using SVN/Ant.

You'll also need the following libraries for server side functionality and/or unit testing:

EasyMockA mock object framework;
log4jA logging framework;
Google Guice 2.0A dependency injection framework for Java. A special build of this is supplied with GIN.

Compiling and Running the Example Code

There is an additional module file called WizardMvpTest which is used by the GWTTestCase based tests and is located under the gwt-test source folder. This module will cause havok with the running of the example in hosted mode and final compilation of the project. When creating a run configuration for hosted mode make sure that the GWTTestModule is excluded. Here are the minimum steps required to get a working run configuration:

  1. From the main menu select, Run -> Run Configurations...
  2. Select "Web Application" from the left hand project type list, right click on this option and choose "New";
  3. Under name, give the configuration a sensible name;
  4. On the "Main" tab, browse to the project "wizard_mvp";
  5. On the "GWT" tab, browse to the URL "wizard_mvp.html" and remove WizardMvpTest from the available modules list;
  6. Select Apply to save the settings and, if you wish, select Run to launch the project.

When compiling the GWT project remove the WizardMvpTest module from the available list - unfortunately this action is not remembered so you'll have to remove it on every compile.

A Whistlestop Tour of the Application

The main focus of this article is to talk about unit testing rather than the application itself, so this section will give a very brief overview of the application. At the server we have a simple Guice servlet servicing commands sent from the client. It maintains a very simple data context which has been pre-populated with some data. At the client we begin with a landing page which contains a table showing vehicles currently in the data model and an add button to launch a wizard for entering data.

Most of us will use wizards fairly frequently (maybe you know them as Process Funnels, Process Guides or Stepped Tasks) and in certain situations they can be a good way to guide a user through some complex task. What makes the wizard interesting the point of view of an MVP app is that:

  • The wizard is a composite of many MVP sub-components, appearing as a single component;
  • Wizard sub-components remain decoupled yet operate on a common state;
  • Other components may require notification of events happening around the wizard.

The wizard in this example comprises a three stage process for entering vehicle data. To keep things interesting, the second stage may branch depending on the vehicle type entered at the first step. Also a navigation panel is used to show progress and provide direct access to previously completed steps.

The wizard is broken down into steps (each of which having a view/presenter pair), a navigation panel and a master vehicle wizard presenter/view. The master presenter maintains a reference to each of the steps and provides the usual wizard action buttons (back, next, etc). The master is fairly dumb and mainly acts as a registry for the steps - it is the steps themselves that contain the more interesting logic such as validation and determining which step to go to next.

The whole point of a wizard is to gather user input and to support this a common state object is injected into each step's presenter which is then responsible for binding and validating the data pertinent to that step. Communication between the master wizard presenter and its components is decoupled via the event bus. For example, if we want to navigate to the next step after the next button is clicked, a "next request" event is fired from the master presenter, a step presenter will listen for these requests. Upon delivery, the step presenter will validate the current step and if all is okay then a response is returned instructing the wizard master presenter of where to go next. Decoupling in this way has the following benefits:

  • A step's logic may be asynchronous. For example if a validation step requires a server call then a Boolean return at that point in time is not be possible. Sending a response message allows us to continue when we're ready;
  • Other components such as the navigation progress presenter can listen for these events and update as necessary (as well as send their own navigation requests) without the step or master wizard ever being aware its existence;
  • The process of unit testing is arguably made easier since a component may be tested with minimal knowledge of neighbouring components.

In this example the master wizard presenter is VehicleWizardPresenter which is a fairly simple affair that just registers the required components:


@Inject
public VehicleWizardPresenter(final Display display,
final EventBus eventBus,
final VehicleStateContext wizardContext,
final WidgetDisplayContainer displayContainer,
final VehicleStepNavigationPresenter vehicleStepNavigationPresenter,
final Step1Presenter step1Presenter,
final Step2CarPresenter step2CarPresenter,
final Step2BoatPresenter step2BoatPresenter,
final Step3Presenter step3Presenter) {
super(display, eventBus, wizardContext);

this.displayContainer = displayContainer;

this.vehicleStepNavigationPresenter = vehicleStepNavigationPresenter;

// Register the steps of the wizard.
registerStep(step1Presenter);
registerStep(step2CarPresenter);
registerStep(step2BoatPresenter);
registerStep(step3Presenter);
}

A step within the wizard is identified by an enumerated type VehicleStepEnum:


public enum VehicleStepEnum {
STEP_1,
STEP_2_CAR,
STEP_2_BOAT,
STEP_3;
}

Each step has a view/presenter pair, for example Step1Presenter/Step1View, and in calling registerStep, the master wizard keys the step presenter against the step enum and is able to call upon this look up when it receives instructions relating to a particular step.

As you navigate between the steps using the back/next buttons or the left hand navigation panel, logic in the steps will validate and determine the next step to visit. A good example of this is in step 1 where the vehicle type selection determines which step to go to next (either car or boat). Step 1 is also interesting since it uses server validation to check that the owner name is unique as well as client side validation.

On the last step a summary of the entered data is presented and the user may hit the Finish button to submit the data to the server.

GWTTestCase vs. JUnit

GWT supports unit testing via the GWTTestCase class which acts as a bridge between the JUnit environment and the GWT environment. This is a very powerful tool and there are definitely situations where this is required (which we'll touch upon in part two), however there is a significant overhead to running tests in this way due to GWTTestCase compiling your GWT code prior to running tests. With Test Driven Development (TDD) we need our tests to execute quickly so we can run them often.

An MVP application lends itself well to plain JUnit testing since most of the business logic is decoupled from the view leaving us with POJOs that can be tested without having to use the GWT compiler. The boundary between the presenter and the view is defined using interfaces like HasClickHandlers which are a perfect fit for using mock object libraries like EasyMock to provide a backing implementation during test execution. Using mock objects one can represent these more complicated view objects and verify interaction without having to rely on the presence of GWT components. As a result we can test our presenter logic in a fraction of the time as tests based on GWTTestCase.

Code quality is also improved when using mocks. Mocks are ideal for validating code in a white box manner allowing you verify that methods on third party components are called as expected.

However, there are occasions where GWTTestCase is required, so our testing strategy will be to have frequently executed plain JUnit tests supported by less frequently executed (slower) GWTTestCase tests. To support this strategy we'll define three source folders in Eclipse:

srcContains our GWT application code;
testContains plain JUnit tests and will mimic the GWT client package structure;
gwt-testContains slower GWTTestCase based tests. Will also mimic the GWT client package structure.

A consequence of separating the plain JUnit tests from the GWTTestCase based ones is that we have sacrificed the ability to run all tests together. However the benefit of running the majority of tests quickly makes this worthwhile.

Getting Started with EasyMock

The aim here is get you up and running quickly with EasyMock and demonstrate some test scenarios under a typical MVP application rather than provide an all-encompassing tour of EasyMock. For a more general introduction and more thorough coverage of EasyMock's features I recommend the following articles:

Rather than give tests that execute perfectly first time, the general approach of this article will be to give an initial example that will fail testing due to omissions or errors in EasyMock configuration. Then we'll cover what went wrong and then fix the tests to work. This kind of follows the learning process I went through and will hopefully give a better understanding of what's going on during testing.

Preparing the Test Environment

By default, your GWT application may not have a test folder. If a test folder is required you can add one from the package explorer, select your project and choose New -> Source Folder. Call the new folder test.

Download and add the EasyMock libraries along with the class extensions from here. At the time of writing, EasyMock and the class extensions were at version 2.5.2 and 2.4 respectively. We use the class extensions to create mocks from classes (rather than just interfaces) to support mock testing of presenters which are typically not coded to interfaces.

Extract the EasyMock distros and add asm-attrs.jar, asm.jar, cglib-2.1.3.jar, easymock-2.5.2.jar and easymockclassextension.jar to your project lib folder and build path.

Under your new test source folder, add a package with the same name as an existing package in your client, e.g. co.uk.hivedevelopment.wizardmvp.client.mvp. This convention is important since it gives your test classes access to protected and package scoped resources which will make life easier and testing more thorough due to greater visibility within the test subject classes.

First Test

Let's start by creating a simple test using EasyMock to make sure we have our test configuration setup correctly.

Right click on the new test package and choose New -> JUnit Test Case.

Select "New JUnit 4 test". If this is the first time you're adding a test then Eclipse will give you the option to automatically include the appropriate JUnit dependencies, which you should do.

Call the test anything you like, but stick to a consistent class naming convention such as suffixing PresenterTest. This convention will come into play later when you may have to specify source exclusion rules in view tests (see "Running View Tests" in part two). In this example we've used FirstPresenterTest.

test creation dialog

Now we have a test case we can add a simple EasyMock test:


@Test
public void testFirstEasyMockTest() {
// Create mock object based on HasClickHandlers interface
final HasClickHandlers clickHandlerMock = createMock(HasClickHandlers.class);

// Set expectation - one call to addClickHandler with an argument
// matching the type "ClickHandler" and returning null.
expect(clickHandlerMock.addClickHandler(isA(ClickHandler.class))).andReturn(null);

// Put mock into replay mode
replay(clickHandlerMock);

// TODO: add test code

// Verify all expected were met.
verify(clickHandlerMock);
}

The basic pattern of mock based testing is as follows:

  1. Create the mock object;
  2. Set the expectation of the mock object;
  3. Put mock object into replay mode;
  4. Perform test;
  5. Verify mock object was called as per our expectations.

When a mock is created, it is set in record mode. When in record mode we set expectations by interacting with the mock object calling methods with specific argument values and/or types and a return value (if necessary). Later we place the mock object into "replay" mode and then run our test against the mock, calling "verify" once we're done. Here EasyMock expects the methods, arguments etc. specified in record mode to match those made during the test. If the expected methods and arguments were called then the test passes.

The test will not pass as we set our expectations but did not interact with the mock between replay and verify, so we will get an exception something like the following:

java.lang.AssertionError:
Expectation failure on verify:
addClickHandler(isA(com.google.gwt.event.dom.client.ClickHandler)): expected: 1, actual: 0

To make this test pass, we need to add some code to interact with the mock:


// ...
replay(clickHandlerMock);

// Run test code
clickHandlerMock.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) { }
});

verify(clickHandlerMock);

Now when you re-run the test it should pass.

Presenter Testing with JUnit and EasyMock

Now we have a test environment and EasyMock configured, how do we test a presenter? Using the gwt-presenter library, a simple presenter might look like the following:


public class ExitPagePresenter extends WidgetPresenter<ExitPagePresenter.Display> {

public static Place PLACE = new Place("ExitPage");

public interface Display extends WidgetDisplay {
HasClickHandlers getReturnToStart();

HasHTML getResult();
}

@Inject
public ExitPagePresenter(final Display display, final EventBus eventBus) {
super(display, eventBus);

bind();
}

@Override
public Place getPlace() {
return PLACE;
}

@Override
protected void onBind() {
display.getReturnToStart().addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
// ...
}
});
}

@Override
public void refreshDisplay() {
display.getResult().setHTML("<h1>Hello Test</h1>");
}

// Empty onPlaceRequest(), onUnbind() and revealDisplay() methods omitted
// for brevity.
}

Some points to note here are that:

  1. GIN was used to inject an instance of the Display and EventBus into the class;
  2. We're calling bind() in the constructor which will cause the framework to invoke onBind().

To start testing an instance of this class, we need to supply instances of Display and EventBus to the presenter's contructor. In the client, instances are created and injected using GIN, however this is not available in a JUnit test. That leaves us with two options: manually create the dependent mock objects and supply them to the presenter's constructor; or we can use to Guice, GIN's Java brother, to provide injection instead. I'm going to discount the manual method since there could be rather a lot of presenters in an application and this will amount to too much wiring. That leaves the Guice option. This makes sense since it uses the same @Inject annotation as GIN allowing us to define injectable components which can be used across all tests.

GIN and Guice

If you're not familiar with Guice, I'll refer you at this point to the following articles by Dick Wall which do a fine job of intro-guicing (sorry!) the concepts:

You should be already using GIN in your project, so Guice is already configured in our environment by way of including the guice-2.0.jar library that is bundled with GIN on the build path. Unfortunately our client GIN module is not reusable in the tests, so our first step is to define a Guice module in which to place our mock bindings:


public class WizardTestModule extends AbstractTestModule {

// ...

@Override
protected void configure() {
final EventBus eventBus = createStrictMock(EventBus.class);
bind(EventBus.class).toInstance(eventBus);

// ...

bindPresenter(ExitPagePresenter.class, ExitPagePresenter.Display.class);

// ...
}
}

Where AbstractTestModule is a Guice version of gwt-presenter's GIN module class AbstractPresenterModule and provides some utility methods for defining presenter/display bindings in much the same way as the client except that mock instances are used. Now we define the bindings once and let Guice do the rest for our tests.

See the accompanying Wizard MVP project for a full definition of AbstractTestModule.

To create an injected instance of the presenter for testing using Guice amend your test's setUp method as follows:


public class FirstPresenterTest {

private EventBus eventBus;
private ExitPagePresenter.Display view;
private ExitPagePresenter presenter;

@Before
public void setUp() throws Exception {
final Injector injector = Guice.createInjector(new WizardTestModule(ExitPagePresenter.class));

// get references to mock objects to verify interaction during test
eventBus = injector.getInstance(EventBus.class);

view = injector.getInstance(ExitPagePresenter.Display.class);

// presenter construction will call bind() which may call view and
// eventBus (so put into replay now).
replay(view, eventBus);

presenter = injector.getInstance(ExitPagePresenter.class);

// Verify calls to mock objects were as expected.
verify(eventBus, view);

// Reset so tests can defined their own expectations.
resetToStrict(eventBus, view);
}

// ...
}

Notice how the constructor for the WizardTestModule refers to the test subject ExitPagePresenter.class? This is important because in our Guice test module we define bindings for all our MVP components as mocks, including ExitPagePresenter.class. Since this class is the test subject injector.getInstance(ExitPagePresenter.class) needs to return an injected real instance otherwise we'll be calling mock code and not the class code. To account for this scenario the AbstractTestModule can take a Class argument indicating that the class type should be ignored during the initialisation which allows us to create the correct presenter test instance.

If you run the test case at this point then the tests will fail because we need to set some expectations. Remember from the ExitPagePresenter code above that the constructor calls bind()? This is turn calls onBind() where our presenter registers a click handler with view.getReturnToStart(). The view object is a mock object and if you recall from the first test we need to specify what is returned from the mock. Also during construction, the gwt-presenter framework also registers a PlaceRequestEvent handler if a presenter returns a non-null object from getPlace(). So we have to define the following expectations to make this test pass:


view = injector.getInstance(ExitPagePresenter.Display.class);

// Set expectation - one call to addClickHandler with an argument matching
// the type "ClickHandler" and returning null.
expect(clickHandlerMock.addClickHandler(isA(ClickHandler.class))).andReturn(null);

// Create mock object based on HasClickHandlers interface
final HasClickHandlers returnToStart = createMock(HasClickHandlers.class);

// Specify that we're expecting one call to view.getReturnToStart() which
// will return our mock click handler.
expect(view.getReturnToStart()).andReturn(returnToStart);

// Required by gwt-presenter
expect(eventBus.addHandler(eq(PlaceRequestEvent.getType()), EasyMock.<PlaceRequestHandler> anyObject())).andReturn(null);

replay(view, eventBus, returnToStart);

presenter = injector.getInstance(ExitPagePresenter.class);

verify(view, eventBus, returnToStart);

Note that we've added returnToStart to the replay/verify list.

This code is still not ideal - it's too verbose and we'll be repeating this functionality in many tests. We can define a utility class to reduce these declarations. The wizard project defines an MvpTestHelper class for this purpose and using some of its methods our setUp becomes:


view = injector.getInstance(ExitPagePresenter.Display.class);

final HasClickHandlers returnToStart =
MvpTestHelper.expectHasClickHandlers(view.getReturnToStart(), ClickHandler.class).getValue();

MvpTestHelper.expectAddAnyHandler(eventBus, PlaceRequestEvent.getType());

replay(view, eventBus, returnToStart);

That reduces the code count somewhat. See "Specifying EasyMock Helper Methods" below for details on creating your own helper methods. The above code verifies that our presenter is created and has made the expected bindings once and only once. This is a crucial check since bugs relating to unbound or multiply bound handlers can be a real menace to track down in an MVP application. I had one such case where a wizard step was registering event handlers twice due a class constructor calling bind() as well as in a super class. At runtime, all sorts of weird exceptions were being thrown and at first it wasn't clear why. As soon as mock testing was introduced the bug was revealed very quickly.

Running the test case now should complete setUp cleanly but we've yet to test the presenter. The following is a simple test to verify that calling refreshDisplay() sets the view HTML:


@Test
public void testRefreshDisplay() {
final String expectedHtml = "<h1>Hello Test</h1>";

final HasHTML html = createMock(HasHTML.class);

expect(view.getResult()).andReturn(html);

html.setHTML(expectedHtml);

replay(view, html);

presenter.refreshDisplay();

verify(view, html);
}

The test above will pass. Try commenting out the call to refreshDisplay() or changing the expected HTML value to see how EasyMock reports the omissions.

Nice and Strict

EasyMock defines different modes of operation. Nice mode creates mocks that aren't too fussy about what's called, how many times (if indeed at all) and in what order. As you might expect, strict mode is the opposite where the expectations must be met or else.

By default the AbstractTestModule will create all mocks in nice mode except for the eventBus and dispatchAsync which are created in strict mode since the order and number of events/server calls are something that is highly important to an MVP application. To crank the mock mode up or down you can use the methods EasyMock.resetToNice() and EasyMock.resetToStrict() as appropriate for your tests.

Specifying EasyMock Helper Methods

As your tests build up you will want to add helper methods of your own. When I first created a helper method I ran into a subtle execution trait with EasyMock which caused my helper to mysteriously fail, so to save you the pain it's probably worth pointing it out here. Let's create a simple helper method to demonstrate the issue. The following code might appear frequently so we want to wrap this up in a static helper method:


// Verify that a click handler was registered
final HasClickHandlers returnToStart = createMock(HasClickHandlers.class);

expect(clickHandlerMock.addClickHandler(isA(ClickHandler.class))).andReturn(null);

expect(view.getReturnToStart()).andReturn(returnToStart);

When creating the helper method, my first (naive) attempt was along the follows lines:


public static HasClickHandlers expectHasClickHandlers(final HasClickHandlers hasClickHandlers) {
final HasClickHandlers mockHasClickHandlers = createMock(HasClickHandlers.class);
expect(hasClickHandlers).andReturn(mockHasClickHandlers);

return mockHasClickHandlers;
}

MvpTestHelper.expectHasClickHandlers(view.getReturnToStart(), ClickHandler.class);

If you run this then you'll get an IllegalStateException with the message "no last call on a mock available". The reason is that when we interact with an EasyMock object, the mock state is maintained for the previous call only and a call to createMock will scrub that state. So in wrapping my code in a static method and the order of the calls to view.getReturnToStart(), expect and createMock had changed resulting in the removal of my mock object state.

After reading the above code you be forgiven for think that the call to view.getReturnToStart() is for its value. However, the return value will be null - the call is actually required because it will arm EasyMock with the mock control context for the specific method call and expect will operate on that context. But this context will not survive long since the first line of the naive helper calls createMock which immediately deletes it and this is the cause of our "no last call on a mock available" message when expect is subsequently called. The solve this, we must correct the call order:


public static HasClickHandlers expectHasClickHandlers(final HasClickHandlers hasClickHandlers) {
final IExpectationSetters<HasClickHandlers> expectationSetter = expect(hasClickHandlers);

final HasClickHandlers mockHasClickHandlers = createMock(HasClickHandlers.class);

expectationSetter.andReturn(mockHasClickHandlers);

return mockHasClickHandlers;
}

Now the call order has been fixed, the helper method runs correctly.

Event Handlers and Anonymous Inner Classes

It's quite common to see examples of GWT code that makes use of anonymous inner classes for defining objects such as click handlers:


display.getAdd().addClickHandler(new ClickHandler() {
public void onClick(final ClickEvent event) {
// logic here
}
});

Code in this manner can be quick to write but it's difficult to test. An initial resolution might be to decouple the business logic from inside onClick into a method which is mutually accessible from the handler and the unit test:


display.getAdd().addClickHandler(new ClickHandler() {
public void onClick(final ClickEvent event) {
doLogic();
}
});

void doLogic() {
// logic here
}

While this solves the issue at hand, we're still left with some downsides in that the call to the doLogic method is not covered by a test and may be subject to typos or may require interaction with click event. Also, if you use a code coverage tool then the code within onClick will still not be reachable from tests which might taint your coverage stats. Finally, if your handler has more than one method to implement then your code becomes increasingly messy. I've found that a tidy solution to all the previous issues is to use an inner class instead:


display.getAdd().addClickHandler(new AddClickHandler());

class AddClickHandler implements ClickHandler {
@Override
public void onClick(final ClickEvent event) {
// logic here
}
}

Note that the class is not static (so we have access to everything we had before) and it uses default scoping to make it accessible from the tests. The line count remains very similar to the original anonymous inner class example only this time we can test the onClick method directly (note the slightly unusual syntax for creating an instance of the inner class):


@Test
public void testAdd() {
// ...

final ClickEvent clickEvent = createMock(ClickEvent.class);

final StartPagePresenter.StartInDialogClickHandler handler = presenter.new StartInDialogClickHandler();

replay(...);

handler.onClick(clickEvent);

verify(...);
}

Summary

In this article you have been introduced to some of the techniques that can applied when unit testing your MVP applications using EasyMock and without the need for using GWTTestCase, resulting in tests run much faster. EasyMock provides a powerful mock solution but using the library for the first time can have some unexpected outcomes, some of which we have covered. I hope this article gets you started with testing your MVP applications. In part two I hope to look at some more specific testing scenaros for an MVP application covering more of the presenter, dispatch actions (commands) and testing the view itself with GWTTestCase.

27 August 2009

Integrating gwt-dispatch with JBoss Seam (and probably Spring!)

In my last post I gave an example based on the Google Webtool Kit (GWT) Starter Application using MVP, dependency injection, event bus and command patterns. The command pattern made use of the excellent gwt-dispatch library and used Google Guice at the server side for wiring the service handlers together. My current project uses JBoss Seam for implementing services and this requires a slightly different configuration so that the server side command handlers are properly injected with Seam managed components.

Therefore the aim of this post is to demonstrate those changes involved so you can integrate your own gwt-dispatch based applications with Seam. I expect that most of the changes here will also apply to services implemented using other server frameworks such as Spring. Naturally, you'll need to appropriately tailor the steps for the Seam specific parts but it should serve as a good starting point nonetheless.

Note 1: I should also point out that the code here demonstrates the bare minimum to get the client talking to GWT and does not take into account how security or validation exceptions from the Seam framework might be handled etc.

Note 2: JBoss Seam does in fact support integration with Guice 1.0. However, since I am writing code from fresh rather than integrating with existing code I feel it is better to produce the command handlers as first class Seam citizens.

I'm assuming that you already have a Seam project configured to talk to GWT via regular RPC (if not, then see the Seam docs on how to do this), you've got the code from the MVP example copied into the project and wish to talk to Seam services instead of the Guice versions.

The approach we'll take is to create our own implementation of the command (a.k.a. Action) dispatch mechanism using standard RPC calls to Seam and then extend the gwt-dispatch default dispatcher implementation to create an ActionHandler instance that has been suitably wrapped by Seam's interceptors. Once this boiler plate code is in place, the original Action and Result code and (more importantly) the calls to them remain unchanged while the action handlers can be modified to use Seam components.

Changes to the Client

  • 1. Define a RPC service to process our commands;
  • 2. Point the client to Seam friendly URLs;
  • 3. Wire it all up.

First up, we need to define an RPC call to carry the command to the server using standard RPC:

DispatchService.java

package co.uk.hivedevelopment.greet.client.gin;

import net.customware.gwt.dispatch.shared.Action;
import net.customware.gwt.dispatch.shared.Result;
import com.google.gwt.user.client.rpc.RemoteService;

public interface DispatchService extends RemoteService {

Result execute(Action<?> action) throws Exception;
}
DispatchServiceAsync.java

package co.uk.hivedevelopment.greet.client.gin;

import net.customware.gwt.dispatch.shared.Action;
import net.customware.gwt.dispatch.shared.Result;
import com.google.gwt.user.client.rpc.AsyncCallback;

public interface DispatchServiceAsync {

void execute(Action<?> action, AsyncCallback<Result> callback);
}

Now we need to change the RPC service URL to a Seam friendly one. By default, gwt-dispatch will look to go to a servlet located at the URL http://<host>/<context>/dispatcher. Seam requires the URL point to http://<host>/<context>/seam/resource/gwt.

SeamDispatchAsync.java

package co.uk.hivedevelopment.greet.client.gin;

import net.customware.gwt.dispatch.client.DispatchAsync;
import net.customware.gwt.dispatch.shared.Action;
import net.customware.gwt.dispatch.shared.Result;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;

public class SeamDispatchAsync implements DispatchAsync {

private static final DispatchServiceAsync realService;

static {
realService = GWT.create( DispatchService.class );
final String endpointURL = getModuleBaseURL() + "seam/resource/gwt";

((ServiceDefTarget) realService).setServiceEntryPoint(endpointURL);
}

public static String getModuleBaseURL() {
// Make sure that communication is with the server that served the containing
// web page and not where the GWT resources came from (which is the case with
// GWT.getHostPageBaseURL)
final String url = GWT.getHostPageBaseURL();

return url;
}

public SeamDispatchAsync() {
}

public <A extends Action<R>, R extends Result> void execute( final A action, final AsyncCallback<R> callback ) {
realService.execute(action, new AsyncCallback<Result>() {

public void onFailure(final Throwable caught) {
callback.onFailure( caught );
}

@SuppressWarnings("unchecked")
public void onSuccess(final Result result) {
callback.onSuccess((R) result);
}
} );
}
}

All standard RPC stuff so far. Finally, to wire everything together we define a new Gin module called SeamDispatchModule and modify the annotation @GinModules on the GreetingGinjector class to use the new SeamDispatchModule rather than the default ClientDispatchModule:

SeamDispatchModule.java

package co.uk.hivedevelopment.greet.client.gin;

import net.customware.gwt.dispatch.client.DispatchAsync;
import com.google.gwt.inject.client.AbstractGinModule;
import com.google.inject.Singleton;

public class SeamDispatchModule extends AbstractGinModule {

@Override
protected void configure() {
bind( DispatchAsync.class ).to( SeamDispatchAsync.class ).in( Singleton.class );
}
}
GreetingGinjector.java

package co.uk.hivedevelopment.greet.client.gin;

import net.customware.gwt.presenter.client.place.PlaceManager;
import com.google.gwt.inject.client.GinModules;
import com.google.gwt.inject.client.Ginjector;
import co.uk.hivedevelopment.greet.client.mvp.AppPresenter;

@GinModules({ SeamDispatchModule.class, GreetingClientModule.class })
public interface GreetingGinjector extends Ginjector {

AppPresenter getAppPresenter();

PlaceManager getPlaceManager();
}

That's it for the client. The existing GreetMvp code was written to use dispatcher interfaces so once we gave Gin an alternative implementation anywhere that uses the dispatcher is injected with new version. Ahhh, good old dependency injection.

Configuration for the Server

Under the GreetMvp example application, all code under the co.uk.hivedevelopment.greet.server.guice package is no longer required. Also if you were already making GWT RPC calls to Seam then there are no changes necessary to your existing Seam web.xml or components.xml. Just make sure that gwt-dispatch jars (aopalliance.jar and gwt-dispatch-1.0.0-SNAPSHOT.jar) are deployed with the application WAR file (i.e. add them to your Eclipse project and edit deployed-jars.list).

At the server we'll:

  • Define the server RPC dispatch implementation;
  • Create a Seam registry for handlers;
  • Modify the SendGreetingHandler for Seam injection.

The following is a standard Seam RPC end point. Notice how the component name matches the fully qualified client side DispatchService remote service interface - this is a key part of any Seam GWT integration:

DispatchServiceImpl.java

package co.uk.hivedevelopment.greet.server.seam;

import net.customware.gwt.dispatch.shared.Action;
import net.customware.gwt.dispatch.shared.Result;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.remoting.WebRemote;
import co.uk.hivedevelopment.greet.client.gin.DispatchService;

@Name("co.uk.hivedevelopment.greet.client.gin.DispatchService")
public class DispatchServiceImpl implements DispatchService {

@In GwtActionDispatcher gwtActionDispatcher;

@WebRemote
@Override
public Result execute(final Action<? extends Result> action) throws Exception {
return gwtActionDispatcher.execute(action);
}
}
GwtActionDispatcher.java

package co.uk.hivedevelopment.greet.server.seam;

import net.customware.gwt.dispatch.server.DefaultDispatch;
import net.customware.gwt.dispatch.shared.Action;
import net.customware.gwt.dispatch.shared.ActionException;
import net.customware.gwt.dispatch.shared.Result;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.Create;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import co.uk.hivedevelopment.greet.server.handler.SendGreetingHandler;

@Name("gwtActionDispatcher")
@Scope(ScopeType.APPLICATION)
@AutoCreate
public class GwtActionDispatcher {

private SeamActionHandlerRegistry actionHandlerRegistry;

@Create
public void init() {
actionHandlerRegistry = new SeamActionHandlerRegistry();

addHandlers();
}

private void addHandlers() {
// TODO: Add all your handlers here.
actionHandlerRegistry.addHandler(new SendGreetingHandler());
}

public Result execute(final Action<? extends Result> action) throws ActionException {
final DefaultDispatch dd = new DefaultDispatch(actionHandlerRegistry);

return dd.execute(action);
}
}
SeamActionHandlerRegistry.java

package co.uk.hivedevelopment.greet.server.seam;

import net.customware.gwt.dispatch.server.ActionHandler;
import net.customware.gwt.dispatch.server.DefaultActionHandlerRegistry;
import net.customware.gwt.dispatch.shared.Action;
import net.customware.gwt.dispatch.shared.Result;
import org.jboss.seam.Component;

public class SeamActionHandlerRegistry extends DefaultActionHandlerRegistry {

@SuppressWarnings("unchecked")
@Override
public <A extends Action<R>, R extends Result> ActionHandler<A, R> findHandler(final A action) {
final ActionHandler<A, R> handler = super.findHandler(action);

if (handler == null) {
return null;
}

// The crucial part to the Seam dispatch implementation is to create handlers using getInstance
final ActionHandler<A, R> handler_ = (ActionHandler<A, R>) Component.getInstance(handler.getClass());

return handler_;
}
}

That completes the server side boiler plate code. The main bit to focus on is the SeamActionHandlerRegistry which extends the default gwt-dispatch implementation to create handlers via Seam's getInstance() method which ensures that appropriate Seam bijection takes place. Remember to add all your new handlers to GwtActionDispatcher.addHandlers().

Finally, here is the version of SendGreetingHandler amended for Seam:

SendGreetingHandler.java

package co.uk.hivedevelopment.greet.server.seam;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import net.customware.gwt.dispatch.server.ActionHandler;
import net.customware.gwt.dispatch.server.ExecutionContext;
import net.customware.gwt.dispatch.shared.ActionException;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.log.Log;
import org.jboss.seam.web.ServletContexts;
import co.uk.hivedevelopment.greet.shared.rpc.SendGreeting;
import co.uk.hivedevelopment.greet.shared.rpc.SendGreetingResult;

@Name("sendGreetingHandler")
@Scope(ScopeType.STATELESS)
public class SendGreetingHandler implements ActionHandler<SendGreeting, SendGreetingResult> {

@Logger Log logger;
@In("org.jboss.seam.web.servletContexts") ServletContexts servletContexts;

@Override
public SendGreetingResult execute(final SendGreeting action,
final ExecutionContext context) throws ActionException {
final String name = action.getName();

try {
final HttpServletRequest request = servletContexts.getRequest();
final ServletContext sc = request.getSession().getServletContext();

final String serverInfo = sc.getServerInfo();
final String userAgent = request.getHeader("User-Agent");

final String message = "Hello, " + name + "! I am running " + serverInfo + ". It looks like you are using:" + userAgent;

return new SendGreetingResult(name, message);
}
catch (Exception cause) {
logger.error("Unable to send greeting", cause);

throw new ActionException(cause);
}
}

@Override
public void rollback(final SendGreeting action,
final SendGreetingResult result,
final ExecutionContext context) throws ActionException {
// Nothing to do here
}

@Override
public Class<SendGreeting> getActionType() {
return SendGreeting.class;
}
}

Good luck!

21 August 2009

Google Web Toolkit (GWT) MVP Example

Here at Hive Development, I'm currently working on the GWT based UI for a new website/RIA monitoring service called SiteAlright. I recommend you head over and check it out. As you might expect we try to follow best practises when developing our apps and there's been quite a lot of talk recently on GWT Google Groups regarding the use of elements from the recent talk by Ray Ryan at Google I/O 2009 entitled Google Web Toolkit Architecture: Best Practices For Architecting Your GWTApp.

This excellent talk laid out several best practise approaches for architecting your GWT application based on the team's experiences while developing the new AdWords interface. Some of the key recommendations were:

The aim of this tutorial is to demonstrate these GWT best practises by applying them the default starter application generated by the Google Plugin for Eclipse.

There are many benefits to these patterns and I'll leave it to the links above to go into these in detail. However, using these patterns the bottom line is that your GWT application will be:

  • Easier to extend - well defined structure makes the addition of new business logic much easier;
  • Easier to test - the decoupling makes quick unit testing of commands, events and presenters not only possible but easy;
  • GWT's History mechanism is baked into your application from the start - this is crucial for a GWT application and is a pain to retro-fit;

MVP Web Application Starter Project

For this tutorial I'm using Eclipse with the Google Plugin for Eclipse. With the Google plugin when you create a GWT project, it also creates a useful starter application with some widgets and server components. This tutorial will work with this starter application and will apply the above patterns.

Although this example has all the source code and references to required resources, you may download the complete GreetMvp project for Eclipse 3.5 from here.

As well as the Google plugin, you'll also need the following GWT libraries:

GWT-PresenterAn implementation of the MVP pattern;
GWT-DispatchAn implementation of the command pattern;
Google GinDependency Injection based on Google's Guice;
GWT-LogA log4j-style logger for GWT.

NOTE: currently GIN needs to be built from source using SVN/Ant.

You'll also need the following libraries at the server:

log4jA logging framework;
Google Guice 2.0A dependency injection framework for Java.

After introducing these best practise patterns, the structure starter application will be transformed. At this point, it's probably worth noting that:

  • You'll no longer make RPC service calls directly - these are wrapped up in the command pattern and are handled by the gwt-dispatch library;
  • The main page and the server response popup will be separated into respective view/presenters;
  • The Event Bus pattern is implemented using the GWT 1.6+ Handlers mechanism.

Unsurprisingly, the code size will jump from the initial starter app, but what you'll end up with offers much more and will hopefully serve as the starting point for a real application based on these best practises.

Let's begin.

Fire up Eclipse and generate your starter application, I've called it GreetMvp:

google plugin eclipse create project 1google plugin eclipse create project 2

This will generate the following structure:

gwt default application structure screenshot

If you're not already familiar with the default application I suggest you take a look at the entry point class which in my case is co.uk.hivedevelopment.greet.client.GreetMvp.java. You'll see all the view code, custom handler logic and server calls all lumped into one method. Fire up the application and you see the following:

gwt default application screenshot

The first thing we'll do is add the required libraries to the project - at this point you should have downloaded the listed dependencies and built Google Gin.

Create a folder called lib in your project at the top level of your project for client-only libraries and add the following:

gin.jar

For server and client dependencies, add the remaining jars into the web application lib folder located at war/WEB-INF/lib:

aopalliance.jar(from Google Gin)
guice-2.0.jar(from Google Gin. IMPORTANT - use the version supplied with Gin and not Guice)
guice-servlet-2.0.jar(from Google Guice)
gwt-dispatch-1.0.0-SNAPSHOT.jar(from gwt-dispatch)
gwt-log-2.6.2.jar(from gwt-log)
gwt-presenter-1.0.0-SNAPSHOT.jar(from gwt-presenter)
log4j.jar(from log4j)

Add all of the above jars to the project's build path. You should have something that looks similar to this:

gwt default application structure screenshot

Edit the GWT module definition file co.uk.hivedevelopment.greet/GreetMvp.gwt.xml:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.7.0//EN" "http://google-web-toolkit.googlecode.com/svn/tags/1.7.0/distro-source/core/src/gwt-module.dtd">
<module rename-to='greetmvp'>
<!-- Inherit the core Web Toolkit stuff. -->
<inherits name="com.google.gwt.user.User" />
<inherits name="com.google.gwt.inject.Inject" />

<inherits name='net.customware.gwt.dispatch.Dispatch' />
<inherits name='net.customware.gwt.presenter.Presenter' />

<!-- Inherit the default GWT style sheet. You can change -->
<!-- the theme of your GWT application by uncommenting -->
<!-- any one of the following lines. -->
<inherits name='com.google.gwt.user.theme.standard.Standard' />
<!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
<!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/> -->

<!-- Specify the app entry point class. -->
<entry-point class='co.uk.hivedevelopment.greet.client.GreetMvp' />

<!-- Add gwt-log support, default level `OFF` - check for
extended property 'log_level' to see if this is overridden -->
<inherits name="com.allen_sauer.gwt.log.gwt-log-OFF" />

<!-- Also compile Logger at `INFO` level -->
<extend-property name="log_level" values="INFO" />
<set-property name="log_level" value="INFO" />

<!-- Turn off the floating logger - output will be shown in the
hosted mode console -->
<set-property name="log_DivLogger" value="DISABLED" />

<source path="shared" />
<source path="client" />
</module>

Note the <source> tags. We have roughly 3 top level packages defined: client, shared and server. We do not want GWT accessing the server sub-packages so we have explicity told the GWT compiler what it can access.

Try to compile the project, it should compile cleanly.

Create View and Presenters



Now we'll split the app into the following components:
AppPresenter.javaRepresents the main application;
GreetingView.javaThe GUI components for the greeting example;
GreetingPresenterThe business logic for the greeting example;
GreetingResponseViewThe GUI for the reponse popup;
GreetingResponsePresenterThe business logic for the response popup.
AppPresenter.java

package co.uk.hivedevelopment.greet.client.mvp;

import net.customware.gwt.dispatch.client.DispatchAsync;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.inject.Inject;

public class AppPresenter {
private HasWidgets container;
private GreetingPresenter greetingPresenter;

@Inject
public AppPresenter(final DispatchAsync dispatcher,
final GreetingPresenter greetingPresenter) {
this.greetingPresenter = greetingPresenter;
}

private void showMain() {
container.clear();
container.add(greetingPresenter.getDisplay().asWidget());
}

public void go(final HasWidgets container) {
this.container = container;

showMain();
}
}
GreetingView.java

package co.uk.hivedevelopment.greet.client.mvp;

import net.customware.gwt.presenter.client.widget.WidgetDisplay;

import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;

public class GreetingView extends Composite implements GreetingPresenter.Display {
private final TextBox name;
private final Button sendButton;

public GreetingView() {
final FlowPanel panel = new FlowPanel();

initWidget(panel);

name = new TextBox();
panel.add(name);

sendButton = new Button("Go");
panel.add(sendButton);

// Add the nameField and sendButton to the RootPanel
// Use RootPanel.get() to get the entire body element
RootPanel.get("nameFieldContainer").add(name);
RootPanel.get("sendButtonContainer").add(sendButton);

reset();
}

public HasValue getName() {
return name;
}

public HasClickHandlers getSend() {
return sendButton;
}

public void reset() {
// Focus the cursor on the name field when the app loads
name.setFocus(true);
name.selectAll();
}

/**
* Returns this widget as the {@link WidgetDisplay#asWidget()} value.
*/
public Widget asWidget() {
return this;
}

@Override
public void startProcessing() {
}

@Override
public void stopProcessing() {
}
}
GreetingPresenter.java

package co.uk.hivedevelopment.greet.client.mvp;

import net.customware.gwt.dispatch.client.DispatchAsync;
import net.customware.gwt.presenter.client.DisplayCallback;
import net.customware.gwt.presenter.client.EventBus;
import net.customware.gwt.presenter.client.place.Place;
import net.customware.gwt.presenter.client.place.PlaceRequest;
import net.customware.gwt.presenter.client.widget.WidgetDisplay;
import net.customware.gwt.presenter.client.widget.WidgetPresenter;
import co.uk.hivedevelopment.greet.shared.event.GreetingSentEvent;
import co.uk.hivedevelopment.greet.shared.rpc.SendGreeting;
import co.uk.hivedevelopment.greet.shared.rpc.SendGreetingResult;
import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.HasValue;
import com.google.inject.Inject;

public class GreetingPresenter extends WidgetPresenter {
/**
* The message displayed to the user when the server cannot be reached or
* returns an error.
*/
private static final String SERVER_ERROR = "An error occurred while "
+ "attempting to contact the server. Please check your network "
+ "connection and try again.";

public interface Display extends WidgetDisplay {
public HasValue getName();

public HasClickHandlers getSend();
}

public static final Place PLACE = new Place("Greeting");

private final DispatchAsync dispatcher;

// FUDGE FACTOR! Although this is not used, having Gin pass the object
// to this class will force its instantiation and therefore will make the
// response presenter listen for events (via bind()). This is not a very good way to
// achieve this, but I wanted to put something together quickly - sorry!
private final GreetingResponsePresenter greetingResponsePresenter;

@Inject
public GreetingPresenter(final Display display,
final EventBus eventBus,
final DispatchAsync dispatcher,
final GreetingResponsePresenter greetingResponsePresenter) {
super(display, eventBus);

this.dispatcher = dispatcher;

this.greetingResponsePresenter = greetingResponsePresenter;

bind();
}

/**
* Try to send the greeting message
*/
private void doSend() {
Log.info("Calling doSend");

dispatcher.execute(new SendGreeting(display.getName().getValue()), new DisplayCallback(display) {

@Override
protected void handleFailure(final Throwable cause) {
Log.error("Handle Failure:", cause);

Window.alert(SERVER_ERROR);
}

@Override
protected void handleSuccess(final SendGreetingResult result) {
// take the result from the server and notify client interested components
eventBus.fireEvent(new GreetingSentEvent(result.getName(), result.getMessage()));
}

});
}

@Override
protected void onBind() {
// 'display' is a final global field containing the Display passed into the constructor.
display.getSend().addClickHandler(new ClickHandler() {
public void onClick(final ClickEvent event) {
doSend();
}
});
}

@Override
protected void onUnbind() {
// Add unbind functionality here for more complex presenters.
}

public void refreshDisplay() {
// This is called when the presenter should pull the latest data
// from the server, etc. In this case, there is nothing to do.
}

public void revealDisplay() {
// Nothing to do. This is more useful in UI which may be buried
// in a tab bar, tree, etc.
}

/**
* Returning a place will allow this presenter to automatically trigger when
* '#Greeting' is passed into the browser URL.
*/
@Override
public Place getPlace() {
return PLACE;
}

@Override
protected void onPlaceRequest(final PlaceRequest request) {
// Grab the 'name' from the request and put it into the 'name' field.
// This allows a tag of '#Greeting;name=Foo' to populate the name
// field.
final String name = request.getParameter("name", null);

if (name != null) {
display.getName().setValue(name);
}
}
}
GreetingResponseView.java

package co.uk.hivedevelopment.greet.client.mvp;

import net.customware.gwt.presenter.client.widget.WidgetDisplay;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHTML;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

public class GreetingResponseView extends DialogBox implements GreetingResponsePresenter.Display {
private final Label textToServerLabel;
private final HTML serverResponseLabel;
private final Button closeButton;

public GreetingResponseView() {
setText("Remote Procedure Call");
setAnimationEnabled(true);

closeButton = new Button("Close");

// We can set the id of a widget by accessing its Element
closeButton.getElement().setId("closeButton");

textToServerLabel = new Label();
serverResponseLabel = new HTML();

final VerticalPanel dialogVPanel = new VerticalPanel();

dialogVPanel.addStyleName("dialogVPanel");
dialogVPanel.add(new HTML("Sending name to the server:"));
dialogVPanel.add(textToServerLabel);
dialogVPanel.add(new HTML("Server replies:"));
dialogVPanel.add(serverResponseLabel);
dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT);
dialogVPanel.add(closeButton);

setWidget(dialogVPanel);
}

public HasText getTextToServer() {
return textToServerLabel;
}

public HasHTML getServerResponse() {
return serverResponseLabel;
}

public HasClickHandlers getClose() {
return closeButton;
}

public DialogBox getDialogBox() {
return this;
}

/**
* Returns this widget as the {@link WidgetDisplay#asWidget()} value.
*/
public Widget asWidget() {
return this;
}

@Override
public void startProcessing() {
}

@Override
public void stopProcessing() {
}
}
GreetingResponsePresenter.java

package co.uk.hivedevelopment.greet.client.mvp;

import net.customware.gwt.presenter.client.EventBus;
import net.customware.gwt.presenter.client.place.Place;
import net.customware.gwt.presenter.client.place.PlaceRequest;
import net.customware.gwt.presenter.client.widget.WidgetDisplay;
import net.customware.gwt.presenter.client.widget.WidgetPresenter;
import co.uk.hivedevelopment.greet.shared.event.GreetingSentEvent;
import co.uk.hivedevelopment.greet.shared.event.GreetingSentEventHandler;
import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.HasHTML;
import com.google.gwt.user.client.ui.HasText;
import com.google.inject.Inject;

public class GreetingResponsePresenter extends WidgetPresenter {
public interface Display extends WidgetDisplay {
HasText getTextToServer();

HasHTML getServerResponse();

HasClickHandlers getClose();

DialogBox getDialogBox();
}

public static final Place PLACE = new Place("GreetingResponse");

@Inject
public GreetingResponsePresenter(final Display display, final EventBus eventBus) {
super(display, eventBus);

bind();
}

@Override
protected void onBind() {
// Add a handler to close the DialogBox
display.getClose().addClickHandler(new ClickHandler() {
public void onClick(final ClickEvent event) {
display.getDialogBox().hide();

// Not sure of a nice place to put these!
// sendButton.setEnabled(true);
// sendButton.setFocus(true);
}
});

eventBus.addHandler(GreetingSentEvent.TYPE, new GreetingSentEventHandler() {

@Override
public void onGreetingSent(final GreetingSentEvent event) {
Log.info("Handling GreetingSent event");

display.getTextToServer().setText(event.getName());
display.getServerResponse().setHTML(event.getMessage());
display.getDialogBox().show();
}
});
}

@Override
protected void onUnbind() {
// Add unbind functionality here for more complex presenters.
}

public void refreshDisplay() {
// This is called when the presenter should pull the latest data
// from the server, etc. In this case, there is nothing to do.
}

public void revealDisplay() {
// Nothing to do. This is more useful in UI which may be buried
// in a tab bar, tree, etc.
}

/**
* Returning a place will allow this presenter to automatically trigger when
* '#GreetingResponse' is passed into the browser URL.
*/
@Override
public Place getPlace() {
return PLACE;
}

@Override
protected void onPlaceRequest(final PlaceRequest request) {
// this is a popup
}
}

At this point, we're still missing some code but hopefully you should start to see the structure coming together.

Events

Since this is a simple application, we only have one event - the GreetingSent event which has a corresponding handler:

GreetingSent.java

package co.uk.hivedevelopment.greet.shared.event;

import com.google.gwt.event.shared.GwtEvent;

public class GreetingSentEvent extends GwtEvent{
public static Type TYPE = new Type();

private final String name;
private final String message;

public GreetingSentEvent(final String name, final String message) {
this.name = name;
this.message = message;
}

public String getName() {
return name;
}

public String getMessage() {
return message;
}

@Override
public Type getAssociatedType() {
return TYPE;
}

@Override
protected void dispatch(final GreetingSentEventHandler handler) {
handler.onGreetingSent(this);
}
}
GreetingSentHandler.java

package co.uk.hivedevelopment.greet.shared.event;

import com.google.gwt.event.shared.EventHandler;

public interface GreetingSentEventHandler extends EventHandler {

void onGreetingSent(GreetingSentEvent event);

}

If you now look at the project references for the event and handler you can see where the events are fired and subsequently handled. The components are blissfully unaware of what produced the event and it just has the information that it needs to get the job done. Now imagine if you want to have another component that also reacts to this event, say to update another part of the GUI. Simple, just register another event handler - no spaghetti code.

RPC

Let's define client RPC. As mentioned earlier, we'll not be making the RPC calls directly. Instead we'll use the command pattern and let the gwt-dispatch library handle the underlying server calls. Although this is an implementation of the command pattern, it turns out that there is already a core GWT class called Command, so the authors of the gwt-dispatch library have opted to use Action instead - so "actions" are really "commands".

In our example, we'll define the Action SendGreeting which represent our server request and a SendGreetingResult class to encapsulate the server response:

SendGreeting.java

package co.uk.hivedevelopment.greet.shared.rpc;

import net.customware.gwt.dispatch.shared.Action;

public class SendGreeting implements Action {

private static final long serialVersionUID = 5804421607858017477L;

private String name;

@SuppressWarnings("unused")
private SendGreeting() {
}

public SendGreeting(final String name) {
this.name = name;
}

public String getName() {
return name;
}
}
SendGreetingResult.java

package co.uk.hivedevelopment.greet.shared.rpc;

import net.customware.gwt.dispatch.shared.Result;

public class SendGreetingResult implements Result {

private static final long serialVersionUID = 7917449246674223581L;

private String name;
private String message;

public SendGreetingResult(final String name, final String message) {
this.name = name;
this.message = message;
}

@SuppressWarnings("unused")
private SendGreetingResult() {
}

public String getName() {
return name;
}

public String getMessage() {
return message;
}
}

At this point, the client code should start to compile but there is still more to do before we can run it. We need some utility classes for the dispatcher and Gin:

CachingDispatchAsync.java

package co.uk.hivedevelopment.greet.client;

import java.util.HashMap;
import java.util.Map;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;
import net.customware.gwt.dispatch.client.DispatchAsync;
import net.customware.gwt.dispatch.shared.Action;
import net.customware.gwt.dispatch.shared.Result;

/**
* Dispatcher which support caching of data in memory
*
*/
public class CachingDispatchAsync implements DispatchAsync {
private DispatchAsync dispatcher;
private Map, Result> cache = new HashMap, Result>();

@Inject
public CachingDispatchAsync(final DispatchAsync dispatcher) {
this.dispatcher = dispatcher;
}

/*
* (non-Javadoc)
* @see net.customware.gwt.dispatch.client.DispatchAsync#execute(A, com.google.gwt.user.client.rpc.AsyncCallback)
*/
public , R extends Result> void execute(final A action, final AsyncCallback callback) {
dispatcher.execute(action, callback);
}

/**
* Execute the give Action. If the Action was executed before it will get fetched from the cache
*
* @param Action implementation
* @param Result implementation
* @param action the action
* @param callback the callback
*/
@SuppressWarnings("unchecked")
public , R extends Result> void executeWithCache(final A action, final AsyncCallback callback) {
final Result r = cache.get(action);

if (r != null) {
callback.onSuccess((R) r);
}
else {
dispatcher.execute(action, new AsyncCallback() {

public void onFailure(Throwable caught) {
callback.onFailure(caught);
}

public void onSuccess(R result) {
cache.put((Action) action, (Result) result);
callback.onSuccess(result);
}

});
}
}

/**
* Clear the cache
*/
public void clear() {
cache.clear();
}
}
GreetingClientModule.java

package co.uk.hivedevelopment.greet.client.gin;

import net.customware.gwt.presenter.client.DefaultEventBus;
import net.customware.gwt.presenter.client.EventBus;
import net.customware.gwt.presenter.client.gin.AbstractPresenterModule;
import net.customware.gwt.presenter.client.place.PlaceManager;
import co.uk.hivedevelopment.greet.client.CachingDispatchAsync;
import co.uk.hivedevelopment.greet.client.mvp.AppPresenter;
import co.uk.hivedevelopment.greet.client.mvp.GreetingPresenter;
import co.uk.hivedevelopment.greet.client.mvp.GreetingResponsePresenter;
import co.uk.hivedevelopment.greet.client.mvp.GreetingResponseView;
import co.uk.hivedevelopment.greet.client.mvp.GreetingView;

import com.google.inject.Singleton;

public class GreetingClientModule extends AbstractPresenterModule {

@Override
protected void configure() {
bind(EventBus.class).to(DefaultEventBus.class).in(Singleton.class);
bind(PlaceManager.class).in(Singleton.class);

bindPresenter(GreetingPresenter.class, GreetingPresenter.Display.class, GreetingView.class);
bindPresenter(GreetingResponsePresenter.class, GreetingResponsePresenter.Display.class, GreetingResponseView.class);

bind(AppPresenter.class).in(Singleton.class);
bind(CachingDispatchAsync.class);
}
}
GreetingGinjector.java

package co.uk.hivedevelopment.greet.client.gin;

import net.customware.gwt.dispatch.client.gin.ClientDispatchModule;
import net.customware.gwt.presenter.client.place.PlaceManager;
import co.uk.hivedevelopment.greet.client.mvp.AppPresenter;
import com.google.gwt.inject.client.GinModules;
import com.google.gwt.inject.client.Ginjector;

@GinModules({ ClientDispatchModule.class, GreetingClientModule.class })
public interface GreetingGinjector extends Ginjector {

AppPresenter getAppPresenter();

PlaceManager getPlaceManager();

}

Now we tie all this together by replacing the existing module entry class with the following:

GreetMvp.java

package co.uk.hivedevelopment.greet.client;

import co.uk.hivedevelopment.greet.client.gin.GreetingGinjector;
import co.uk.hivedevelopment.greet.client.mvp.AppPresenter;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.RootPanel;

public class GreetMvp implements EntryPoint {
private final GreetingGinjector injector = GWT.create(GreetingGinjector.class);

public void onModuleLoad() {
final AppPresenter appPresenter = injector.getAppPresenter();
appPresenter.go(RootPanel.get());

injector.getPlaceManager().fireCurrentPlace();
}
}

Server Components

Okay, now let's define the server side. We need to configure Guice and the dispatch handler plus we need to provide an implementation for the SendGreeting action.

SendGreetingHandler.java

package co.uk.hivedevelopment.greet.server.handler;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import net.customware.gwt.dispatch.server.ActionHandler;
import net.customware.gwt.dispatch.server.ExecutionContext;
import net.customware.gwt.dispatch.shared.ActionException;
import org.apache.commons.logging.Log;
import co.uk.hivedevelopment.greet.shared.rpc.SendGreeting;
import co.uk.hivedevelopment.greet.shared.rpc.SendGreetingResult;
import com.google.inject.Inject;
import com.google.inject.Provider;

public class SendGreetingHandler implements ActionHandler<SendGreeting, SendGreetingResult> {
private final Log logger;
private final Provider<ServletContext> servletContext;
private final Provider<HttpServletRequest> servletRequest;

@Inject
public SendGreetingHandler(final Log logger,
final Provider<ServletContext> servletContext,
final Provider<HttpServletRequest> servletRequest) {
this.logger = logger;
this.servletContext = servletContext;
this.servletRequest = servletRequest;
}

@Override
public SendGreetingResult execute(final SendGreeting action,
final ExecutionContext context) throws ActionException {
final String name = action.getName();

try {
String serverInfo = servletContext.get().getServerInfo();

String userAgent = servletRequest.get().getHeader("User-Agent");

final String message = "Hello, " + name + "!<br><br>I am running " + serverInfo
+ ".<br><br>It looks like you are using:<br>" + userAgent;


//final String message = "Hello " + action.getName();

return new SendGreetingResult(name, message);
}
catch (Exception cause) {
logger.error("Unable to send message", cause);

throw new ActionException(cause);
}
}

@Override
public void rollback(final SendGreeting action,
final SendGreetingResult result,
final ExecutionContext context) throws ActionException {
// Nothing to do here
}

@Override
public Class<SendGreeting> getActionType() {
return SendGreeting.class;
}
}
DispatchServletModule.java

package co.uk.hivedevelopment.greet.server.guice;

import net.customware.gwt.dispatch.server.service.DispatchServiceServlet;
import com.google.inject.servlet.ServletModule;

public class DispatchServletModule extends ServletModule {

@Override
public void configureServlets() {
// NOTE: the servlet context will probably need changing
serve("/greetmvp/dispatch").with(DispatchServiceServlet.class);
}

}
LogProvider.java

package co.uk.hivedevelopment.greet.server.guice;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.impl.Log4JLogger;
import com.google.inject.Provider;
import com.google.inject.Singleton;

@Singleton
public class LogProvider implements Provider<Log>{

public Log get() {
return new Log4JLogger("GreetingLogger");
}

}
MyGuiceServletConfig.java

package co.uk.hivedevelopment.greet.server.guice;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceServletContextListener;

public class MyGuiceServletConfig extends GuiceServletContextListener {

@Override
protected Injector getInjector() {
return Guice.createInjector(new ServerModule(), new DispatchServletModule());
}
}
ServerModule.java

package co.uk.hivedevelopment.greet.server.guice;

import net.customware.gwt.dispatch.server.guice.ActionHandlerModule;
import org.apache.commons.logging.Log;
import co.uk.hivedevelopment.greet.server.handler.SendGreetingHandler;
import com.google.inject.Singleton;

/**
* Module which binds the handlers and configurations
*
*/
public class ServerModule extends ActionHandlerModule {

@Override
protected void configureHandlers() {
bindHandler(SendGreetingHandler.class);

bind(Log.class).toProvider(LogProvider.class).in(Singleton.class);
}
}

Finally we need to reconfigure the web.xml with the following:

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

<!-- Remote logging agent for gwt-log -->
<servlet>
<servlet-name>remoteLoggerServiceImpl</servlet-name>
<servlet-class>com.allen_sauer.gwt.log.server.RemoteLoggerServiceImpl</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>remoteLoggerServiceImpl</servlet-name>
<url-pattern>/greetmvp/gwt-log</url-pattern>
</servlet-mapping>


<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<listener>
<listener-class>co.uk.hivedevelopment.greet.server.guice.MyGuiceServletConfig</listener-class>
</listener>

<!-- Default page to serve -->
<welcome-file-list>
<welcome-file>GreetMvp.html</welcome-file>
</welcome-file-list>

</web-app>

You may now launch the application. Surprise surprise, it looks no different but you're now following best practises. Good luck with your own GWT MVP development.

gwt default application screenshot (after)