-
Notifications
You must be signed in to change notification settings - Fork 52
Getting started
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.
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.
Ok, you now have a very simple "Hello World!" application. The most logical way to test it would be something like:
- Given: Fill the textbox
- When: Click the 'Send' button
- Then: 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
GWTTestCaseto wait x milliseconds for server response.. - Manually cleanup the DOM after each
@Testmethod by overriding the gwtTearDown() method.
- Add all librairies required by gwt-test-utils :
- gwt-test-utils-X.jar
- javassist-3.20.0-GA+.jar
- junit-4.5+.jar
- slf4j-api-1.6+.jar
- assertj-core-3.4.1+.jar
- 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.
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.
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 GWTGWTTestCase. -
You also must annotate your test class with
GwtModuleto tell gwt-test-utils which module is under test. Be carefull to use a module you've declared in yourMETA-INF/gwt-test-utils.propertieswith 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 assertj 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() {
// Given: Fill the textbox
Browser.fillText(nameField(), "World");
// When: Click the 'Send' button
Browser.click(sendButton());
// Then: 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
BrowserAPI 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 ;-)
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.
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) {
// org.springframework.mock.web.MockServletConfig from spring-test
return new MockServletConfig();
}
@Override
public HttpServletRequest getMockedRequest(AbstractRemoteServiceServlet rpcService, Method rpcMethod) {
// mock the user-agent with org.springframework.mock.web.MockHttpServletRequest from spring-test
MockHttpServletRequest mock = MockHttpServletRequest();
mock.addHeader("User-Agent", "mocked-user-agent");
return mock;
}
});
...
}With this configuration, you can safely expect that:
getServletContext().getServerInfo(); // always returns "MockServletContext" (hardcoded in org.springframework.mock.web.MockServletConfig)
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 MockServletContext.<br><br>It looks like you are using:<br>mocked-user-agent");
assertThat(errorLabel()).isVisible().textEquals("");It should been green now :-)
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() {
// Given
Browser.fillText(nameField(), "123");
// When
Browser.click(sendButton());
// Then
assertThat(dialogBox()).isNotShowing();
assertThat(errorLabel()).isVisible().textEquals("Please enter at least four characters");
}Very simple, isn't it ?
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() {
// Given
Browser.fillText(nameField(), "Enter");
Event keyUpEvent = EventBuilder.create(Event.ONKEYUP).setKeyCode(KeyCodes.KEY_ENTER).build();
// When
Browser.dispatchEvent(nameField(), keyUpEvent);
// Then
assertThat(dialogBox()).isShowing().textEquals("Remote Procedure Call");
assertThat(serverResponseLabel()).htmlEquals("Hello, Enter!<br><br>I am running MockServletContext.<br><br>It looks like you are using:<br>mocked-user-agent");
assertThat(errorLabel()).isVisible().textEquals("");
}-
In the layout phase, a GWT
Eventis created through the gwt-test-utilsEventBuilder. It has been designed to build very complex events easily. -
Browser.dispatchEventis 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 :-)
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.