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