Skip to content
twistedpair edited this page Feb 3, 2013 · 15 revisions

Since gwt-test-utils is a testing framework, this page won't explain how to develop a "Hello World !" application, but how to test a GWT "Hello world!" application instead :-)\ Therefore, the purpose of this page is to help you setting up gwt-test-utils basics and writing your first test in under 5 minutes.

If you're not familiar with GWT yet... well, it would be hard to have a GWT app and some unit tests working in 5 minutes! You should consider the GWT official Getting Started before continuing with gwt-test-utils.

Creating the GWT "Hello World !" application

Let's create an out-of-the-box GWT application with the GWT plugin for eclipse :

  • Select File > New > Web Application Project from the Eclipse menu.

  • In the New Web Application Project wizard, enter a name for your project : GwtTestSample and a java package name : com.sample.mywebapp.

  • Be sure Generate GWT sample code is checked

  • Click Finish.

Launch the generated 'GwtTestSample' application. It is very basic :

  • one label
  • one textbox
  • one 'Send' button

Fill the textbox with 'World', and click the button. The client calls the server which validate the text has at least 4 characters and fill a DialogBox with server-side generated HTML 'Hello, World!'. This is a typical GWT use case.

Time to test

Ok, you now have a very simple "Hello World!" application. The most logical way to test it would be something like:

  • Arrange: Fill the textbox
  • Act: Click the 'Send' button
  • Assert: Check the dialogbox is shown and its html is as expected

But testing it with GWTTestCase would not be so simple. You would have to :

  • Launch a jetty instance. Ok, this is done behind the scene by GWTTestCase, but it's very, very slow...
  • Design your unit test to be compatible with the asynchronous server call, e.g., tell GWTTestCase to wait x milliseconds for server response..
  • Manually cleanup the DOM after each @Test method by overriding the gwtTearDown() method.

Let's do this with gwt-test-utils

Configuration in the project

  1. Add all librairies required by gwt-test-utils :
  • gwt-test-utils-X.jar
  • javassist-3.12.1.GA+.jar
  • junit-4.5+.jar
  • slf4j-api-1.6+.jar
  • fest-assert-core-2.0M7+.jar
  • fest-util-1.2.2+.jar
  1. Create a META-INF/gwt-test-utils.properties file in your test classpath, e.g., in "test" directory. Add this line in it :
    com.sample.mywebapp.GwtTestSample = gwt-module

You have to declare all your modules this way to let gwt-test-utils know which classes it will have to operate bytecode transformations on.

Note that you won't have to declare inherited modules. In your sample application, it would be useless to declare com.google.gwt.user.User = gwt-module since you have the corresponding inherits declaration in your GwtTestSample.gwt.xml.

Usefull modifications

At the end of GwtTestSample.onModuleLoad method, add an HTML id to each widget you'll need easy access to:

public class GwtTestSample implements EntryPoint {
  
  public void onModuleLoad() {
    ...
    sendButton.getElement().setId("sendButton");
    nameField.getElement().setId("nameField");
    errorLabel.getElement().setId("errorLabel");
    dialogBox.getElement().setId("dialogBox");
    textToServerLabel.getElement().setId("textToServerLabel");
    serverResponseLabel.getElement().setId("serverResponseLabel"); 
  }
}

This is absolutely not mandatory, you could retrieve each widget directly from the RootPanel instance where it is registered. For Example : TextBox nameField = (TextBox) RootPanel.get("nameFieldContainer").getWidget(0);. But it would not be very flexible: your test would need to know your exact DOM structure. Changing it would break your test.

In real projects, you also could set debug ids instead not to pollute your production's code with real ids.

Write the first test class

Now, you can start writing your first test class, in the com.sample.mywebapp.clientpackage:

import static com.googlecode.gwt.test.assertions.GwtAssertions.assertThat;
import static com.googlecode.gwt.test.finder.GwtFinder.object;

@GwtModule("com.sample.mywebapp.GwtTestSample")
public class GwtTestSampleTest extends GwtTest {

  private GwtTestSample app;
  
  @Before
  public void before() {
    app = new GwtTestSample();
    app.onModuleLoad();

    // Some pre-assertions
    assertThat(dialogBox()).isNotShowing();
    assertThat(errorLabel()).isVisible().textEquals("");
  }
  
  private DialogBox dialogBox() {
    return object("dialogBox").ofType(DialogBox.class);
  }
  
  private Label errorLabel() {
    return object("errorLabel").ofType(Label.class);
  }

}
  • The test class must extend GwtTest, which is the gwt-test-utils alternative to GWT GWTTestCase.

  • You also must annotate your test class with GwtModule to tell gwt-test-utils which module is under test. Be carefull to use a module you've declared in your META-INF/gwt-test-utils.properties with the 'gwt-module' key/value pair. Otherwise, an exception would be thrown.

  • Before each test method, create and initialize a new app by calling the EntryPoint.onModuleLoad() method on it. The simple "pre assertions" demonstrate three important things:

  • the DOM is automatically reinitialize between two tests :-)

  • You can use the GwtFinder.object(...) API to retrieve Widget instances very easily, based on their DOM id for example.

  • You can use the GwtAssertions.assertThat(...) API to make fluent assertions on GWT widgets (it's based on the awesome fest-assert library).

Note: for a comfortable use of those APIs, we strongly recommend importing both GwtFinder and GwtAssertions utilities statically (How to do this easily in Eclipse).

Now it's time to write the unit test we were talking about:

@Test
public void clickOnSendMoreThan4chars() {
  
  // Arrange: Fill the textbox
  Browser.fillText(nameField(), "World");

  // Act: Click the 'Send' button
  Browser.click(sendButton());

  // Assert: Check the dialogbox is shown...
  assertThat(dialogBox()).isShowing().textEquals("Remote Procedure Call");
  assertThat(errorLabel()).isVisible().textEquals("");
}

private TextBox nameField() {
  return object("nameField").ofType(TextBox.class);
}
  
private Button sendButton() {
  return object("sendButton").ofType(Button.class);
}
  
private HTML serverResponseLabel() {
  return object("serverResponseLabel").ofType(HTML.class);
}
  • This first unit test shows how to use the Browser API to simulate any DOM event of your choice.

Like for GwtFinder and GwtAssertions, you could add the Browser class to your favorite static types. The choice is yours ;-)

Run this test...

DAMN IT'S RED :-(

Don't worry, the thrown GwtTestRpcException was expected ;-) Have a look at the full error message:

"Illegal call to com.example.mywebapp.server.GreetingServiceImpl.getServletConfig() : You have to set a valid ServletMockProvider instance through GwtTestSamleTest.setServletMockProvider(..) method."

In GreetingServiceImpl.greetServer(...) implementation, we have those two lines:

String serverInfo = getServletContext().getServerInfo();
String userAgent = getThreadLocalRequest().getHeader("User-Agent");

GreetingServiceImpl, which is a javax.servlet.http.HttpServlet implementation, relies on ServletConfig.getServletContext() and HttpServletRequest to retrieve serverInfo and userAgent data. Since gwt-test-utils doesn't emulate the entire servlet stack, you'll have to mock it at some point.

Mocking javax.servlet API

In some cases like above, your RemoteServices implementation will need access to the servlet API. When testing such code in a GwtTest, mocking those access is mandatory and very easy to do.

gwt-test-utils provides a ServletMockProvider interface which will be used every time a RemoteServiceServlet implementation calls getThreadLocalRequest(), getThreadLocalResponse() or getServletConfig(). Custom implementations of this interface are setted using the protected setServletMockProvidermethod available in your GwtTest subclasses.

Here is you to setup a simple ServletMockProvider in your GwtTestSampleTest class:

@Before
public void before() {
  // use the provided adapter to implement only the methods you need for your test
  setServletMockProvider(new ServletMockProviderAdapter() {
  
    @Override
    public ServletConfig getMockedConfig(AbstractRemoteServiceServlet remoteService) {
      // mock the serverInfo in ServerConfig
      MockServletContext context = new MockServletContext();
      context.setServerInfo("mocked-server-info");
      
      return new MockServletConfig(context);
    }
    
    @Override
    public HttpServletRequest getMockedRequest(AbstractRemoteServiceServlet rpcService, Method rpcMethod) {
      // mock the user-agent header in HttpServletRequest
      MockHttpServletRequest mock = new MockHttpServletRequest();
      mock.addHeader("User-Agent", "mocked-user-agent");
      
      return mock;
    }
  });
  
  ...
  
}

With this configuration, you can safely expect that:

getServletContext().getServerInfo(); // always returns "mocked-server-info"
getThreadLocalRequest().getHeader("User-Agent"); // always returns "mocked-user-agent"

Note: gwt-test-utils comes with a set of mock implementations for most servlet API objects you may want to use to build your mocks: MockHttpServletRequest, MockHttpServletResponse, MockHttpSession, MockServletConfig, MockServletContext, MockRequestDispatcher.

Now that you have mocked serverInfo and userAgent data, you know exactly what should be displayed in the displayed popup. Change the assertions part of your unit test like this:

// Assert: Check the dialogbox is shown and its html is like expected
assertThat(dialogBox()).isShowing().textEquals("Remote Procedure Call");
assertThat(serverResponseLabel()).htmlEquals("Hello, World!<br><br>I am running mocked-server-info.<br><br>It looks like you are using:<br>mocked-user-agent");
assertThat(errorLabel()).isVisible().textEquals("");

Re-run the test...

It should been green now :-)

More tests, pleaaase !

Now that you have our first green test, you may want write some others, because having a lot of green tests is cool! So, let's check an error is displayed when filling a less than 4 character String in the textbox:

@Test
public void clickOnSendLessThan4chars() {
  // Arrange
  Browser.fillText(nameField(), "123");

  // Act
  Browser.click(sendButton());
  
  // Assert
  assertThat(dialogBox()).isNotShowing();
  assertThat(errorLabel()).isVisible().textEquals("Please enter at least four characters");
}

Very simple, isn't it ?

Simulating complex DOM events ? Easy win !

You may have noticed that the generated sample code handles a "press enter" action, which also sends the text to the server. You may want to automatically test it:

@Test
public void pressEnterMoreThan4chars() {
  // Arrange
  Browser.fillText(nameField(), "Enter");
  Event keyUpEvent = EventBuilder.create(Event.ONKEYUP).setKeyCode(KeyCodes.KEY_ENTER).build();
  
  // Act
  Browser.dispatchEvent(nameField(), keyUpEvent);
  
  // Assert
  assertThat(dialogBox()).isShowing().textEquals("Remote Procedure Call");
  assertThat(serverResponseLabel()).htmlEquals("Hello, Enter!<br><br>I am running mocked-server-info.<br><br>It looks like you are using:<br>mocked-user-agent");
  assertThat(errorLabel()).isVisible().textEquals("");
}
  • In the layout phase, a GWT Event is created through the gwt-test-utils EventBuilder. It has been designed to build very complex events easily.

  • Browser.dispatchEvent is a generic method to simulate events your own custom events.

Note: Actually, the Browser class provides a lot of helper methods, such as Browser.keyUp(Widget, KeyCode) which would be more appropriate for this test. But we just wanted to introduce the EventBuilder here :-)

Conclusion

Well, this was just an introduction about what gwt-test-utils is able to do. If you are interested in the many other features the framework provides, you should consider reading the complete documentation (which isn't to long ;-))

Of course, the GwtTestSample eclipse project can be downloaded under the Download section.