GWT Contacts Demo with MVP and Testing

2

On Monday, January 23th, I am going to talk about some cool features of the Google Web Toolkit (GWT) at the OOP 2012 conference in Munich. To have a live demo for discussion, I implemented a GWT port of my Eclipse 4 Contacts Demo. In this blog post, I talk a bit about my experience with Google’s MVP (Model View Presenter) approach, including activities, places and event bus. Furthermore about UIBinder in combination with GWT Designer and Presenter testing using Mockito.

You can find the latest source of the demo at https://github.com/toedter/gwt-mvp-contacts. Here is a screen shot, as you see, I did not focus on nice design. Click on the image to see it in original size.

Model View Presenter (MVP)

The Model View Presenter Pattern has different flavors: “Passive View” and “Supervising controller“. In Passive View there is no dependency between model and view, Supervising Controller is not so strict and allows e.g. data binding from model to view. In GWT, presenters are represented by Activities, views are implemented by GWT UI classes. There are 2 common ways to define the binding between views and presenters: Either the presenter defines a display interface that the view has to implement, or the view interface provides a presenter interface. The latter one is often used in GWT when the UI is created with UIBinder, because it makes it easier for the view to call back to his presenter. In the contacts demo, the view interface for the list view looks like

public interface IContactListView extends IsWidget, AcceptsOneWidget {
  public interface Presenter {
    void goTo(Place place);
    void select(int index);
    void select(Contact contact);
  }

  void setPresenter(Presenter presenter);
  void selectInitialContact(Contact contact);
  void initialize(List contacts);
}

As you see, for laziness reasons I don’t use “Passive View” since I pass a domain object (Contact) to the view. A good introduction into GWT MVP you find at the GWT Web site. If you have a more application-like layout with kind of toolbar and several display regions, you might think you will need nested presenters/activities, but you don’t have to nest them. Thomas Broyer wrote a nice article about this issue, and I used his approach in my demo.

UIBinder and GWT Designer

UIBinder splits a UI component into a Java part and an XML/CSS part. That makes it easier to separate concerns and helps designers and developers working together. GWT Designer is a nice and free GWT UI Editor for Eclipse. One feature is the automatic creation of MPV related infrastructure, like corresponding activities and places. In the demo I have several implementations of the list view and the details view. You can change them by altering the ClientFactory implementation. The screen shot below shows a small details view example in GWT Designer, klick on it to see the image in original size.

Presenter Testing with mockito

One advantage of MVP based approaches is the good testability. For that purpose I wrote a few exemplary tests using mockito as Mocking framework. The reason for using plain JUnit tests together with mocks is mostly speed. With mockito it is pretty easy to mock all the involved GWT MVP infrastructure like activities, places, event bus and even a GWT Remote Procedure Call. Here is an example for a presenter test:

@RunWith(MockitoJUnitRunner.class)
public class ContactListActivityTest {

	@Mock
	private IClientFactory clientFactoryMock;

	@Mock
	private PlaceController placeControllerMock;

	@Mock
	private IContactListView contactListViewMock;

	@Mock
	private AcceptsOneWidget acceptsOneWidgetMock;

	@Mock
	private IContactServiceAsync contactServiceAsyncMock;

	@Mock
	private EventBus eventBusMock;

	private List contacts;
	private Contact contact1;
	private Contact contact2;

	@SuppressWarnings("unchecked")
	@Before
	public void setUp() throws Exception {
		when(clientFactoryMock.getPlaceController()).thenReturn(placeControllerMock);
		when(clientFactoryMock.getContactListView()).thenReturn(contactListViewMock);
		when(clientFactoryMock.getContactService()).thenReturn(contactServiceAsyncMock);

		// Mock a GWT RPC
		Answer answer = new Answer() {
			@Override
			public Void answer(InvocationOnMock invocation) {
				Object[] args = invocation.getArguments();
				AsyncCallback> asyncCallback = (AsyncCallback>) args[0];
				contact1 = new Contact();
				contact1.setFirstName("Kai");
				contact1.setLastName("Toedter");
				contact1.setEmail("kai@toedter.com");
				contact2 = new Contact();
				contact2.setFirstName("Kai2");
				contact2.setLastName("Toedter2");
				contact2.setEmail("kai2@toedter.com");
				final List contacts2 = new ArrayList();
				contacts2.add(contact1);
				contacts2.add(contact2);
				asyncCallback.onSuccess(contacts2);
				return null;
			}
		};

		doAnswer(answer).when(contactServiceAsyncMock).
                getAllContacts(any(AsyncCallback.class));

		// set the real contacts object, when clientFactory.setContacts is
		// called
		Answer setContactsAnswer = new Answer() {
			@Override
			public Void answer(InvocationOnMock invocation) throws Throwable {
				contacts = (List) invocation.getArguments()[0];
				// System.out.println("answer() to setContacts(): " + contacts);
				return null;
			}
		};

		doAnswer(setContactsAnswer).when(clientFactoryMock).setContacts(any(List.class));

		// Return the real contacts object, when clientFactory.getContacts is
		// called
		Answer> getContactsAnswer = new Answer>() {
			@Override
			public List answer(InvocationOnMock invocation) throws Throwable {
				return contacts;
			}
		};

		doAnswer(getContactsAnswer).when(clientFactoryMock).getContacts();
	}

	@Test
	public void testGotoPlace() {
		ContactListActivity contactListActivity = 
                new ContactListActivity(new ContactPlace(null), clientFactoryMock);

		ContactPlace contactPlace = new ContactPlace("kai@toedter.com");
		contactListActivity.goTo(contactPlace);

		verify(placeControllerMock).goTo(contactPlace);
	}

	@Test
	public void testStartWithEmptyToken() {
		clientFactoryMock.setContacts(null); // force RCP
		ContactListActivity contactListActivity = 
                   new ContactListActivity(new ContactPlace(""), clientFactoryMock);
		contactListActivity.start(acceptsOneWidgetMock, eventBusMock);

		verify(contactListViewMock).setPresenter(contactListActivity);
		verify(contactListViewMock).initialize(contacts);
	}

	@Test
	public void testStartWithToken() {
		String token = "kai@toedter.com";
		clientFactoryMock.setContacts(null); // force RCP

		ContactListActivity contactListActivity = 
                new ContactListActivity(new ContactPlace(token), clientFactoryMock);
		contactListActivity.start(acceptsOneWidgetMock, eventBusMock);

		verify(contactListViewMock).setPresenter(contactListActivity);
		verify(contactListViewMock).initialize(contacts);
		verify(contactListViewMock).selectInitialContact(contact1);
		verify(eventBusMock).fireEvent(any(ContactViewEvent.class));
	}

	@Test
	public void testMayStop() {
		ContactListActivity contactListActivity = 
                new ContactListActivity(new ContactPlace(null), clientFactoryMock);
		contactListActivity.start(acceptsOneWidgetMock, eventBusMock);
		contactListActivity.mayStop();

		verify(contactListViewMock).setPresenter(null);
	}

	@Test
	public void clientFactoryTest() {
		List testList = new ArrayList();
		clientFactoryMock.setContacts(testList);
		Assert.assertNotNull(clientFactoryMock.getContacts());
	}
}

If you are interested in above stuff and happen to be in Munich on January 23th 2012, join my Night School, 6:30pm at the OOP 2012.

Have Fun!

Kai

You find me on Twitter and Google+.
Next Eclipse RCP 3.x/4.x trainings in Munich

2 Comments

  1. Raphael Brugier01-18-2012

    Hi,

    I’m looking forward to see your presentation. Will it be available online ?

    For the usage of mockito, I suggest to rewrite the RPC call stubbing in a more BDD style way:

    import static org.mockito.BDDMockito.* given(clientFactoryMock.getContacts()).willReturn(contacts);

    This will save you the creation of the Answer inner class.

    Btw, I’m still not sure if this stubbing of the rpc calls are required or not when testing your Presenter. In the GTAC 2009 talk: “Even Better Than the Real Thing – Lessons Learned” (slides are offline now :/ ) they were suggesting to handle the response of the rpc call as separated methods and tests this methods separately.

    I’d like to have your point of view on this?

    Thanks,
    Raphaël

  2. Kai Tödter01-20-2012

    @Raphael, thanks for the tips, will take a look at it.

Leave a Reply