Skip to content
Gael Lazzari edited this page Sep 10, 2012 · 6 revisions

In UI tests, you'll probably want to check that expected behaviors do occur when the user interacts with the UI components, like clicking on a button or filling a text box.

gwt-test-utils provides ways to simulate this user interaction and the corresponding DOM events. So let's talk about how a web browser is simulated !

Using predefined events

In the previous sample, we wanted to test that a message is displayed when the user fills the textbox and then clicks on the button. We can achieve this by simulating the 'fill text' action and a DOM 'click' event using the static methods Browser.fillText() and Browser.click():

@Test
public void clickOnButtonShouldDisplayMessageInLabel() {
  // Arrange
  SampleView view = new SampleView();
  // ensure the widgets state at init
  assertThat(view.label).isNotVisible();
  assertThat(view.button).isVisible().isNotEnabled();
  
  // Act  
  Browser.fillText(view.textBox, "Jack Shephard");
  // the button should be enabled now
  Browser.click(view.button);
  
  // Assert: label should be visible and filled
  assertThat(view.label).isVisible().textEquals("Hello Jack Shephard");
}

Browser provides many predefined methods to simulate various events, like blur(), change(), etc.

Using custom events

Your application might also react to some slightly more complex events, like pressing a combination of keys.
For such a case, you can simulate this kind of event by using an EventBuilder to build it, and then firing it with the dispatchEvent() method of Browser :

@Test
public void complexClickOnButtonShouldDisplayMessageInLabel() {
  // Arrange
  SampleView view = new SampleView();
  view.textBox.setText("Ben Linus");
  view.button.setEnabled(true);
  assertThat(view.label).isNotVisible();
  
  // Act : simulate a complex event (ctrl + shift + click)
  Event clickEvent = EventBuilder.create(Event.ONCLICK)
    .setCtrlKey(true)
    .setShiftKey(true)
    .build();
  Browser.dispatchEvent(button, clickEvent);
  
  // Assert: label should be visible and filled
  assertThat(view.label).isVisible().textEquals("Hello Ben Linus");
}

Events validation

In real life, your favorite web browser won't allow you to trigger any event on a disabled or display:none UI components, such as text boxes, buttons, etc.
The Browser class does the same by checking the targeted widget state before dispatching any event on it.

There is a special case for attached / detached widgets. Real browsers won't let you trigger events on detached widgets, since they aren't displayed on screen. But Browser class by default will be able to dispatch any event on a detached widget.
Actually, we decided that having to add a widget we want to unit test to the GWT RootPanel (or RootLayoutPanel) so it will be attached to the fake DOM was an unnecessary constraint.

This behavior can easily be overrided if you want Browser to check for a widget attached / detached state before dispatching events on it. In your test class, just use the GwtTest.setCanDispatchEventsOnDetachedWidgets(...) protected method like that:

@Before
public void setupBrowser() {
  setCanDispatchEventsOnDetachedWidgets(false);
}

Just keep in mind this feature is reseted to true between each tests.

Browser's event loop simulation

With JavaScript, most events are asynchronous. When an asynchronous event occurs, it gets into the Event queue.

Real web browsers have an inner loop, called Event Loop, which checks the queue and processes events, executes functions etc.

gwt-test-utils browser implementation doesn't have a real Event Loop, since your tests are executed in a thread without any pause to notify this loop it can processed queued events, DOM modifications, ScheduledCommand, async callbacks, etc.
So, when you attach some Widget to the DOM, it's immediately attached.

But this 'synchronous' behavior is not relevant in some cases, where you explicitly want to invoke code with some delay :

For those cases, gwt-test-utils use an internal queue of commands which are not executed until they are explicitly told.

Concretely

If you got confused, don't worry. Code always explains things better :

@GwtModule("com.googlecode.gwt.test.sample.Sample")
public class SampleViewTest extends GwtTest {

  @Test
  public void deferredCommandShouldNotBeTriggerSynchronously() {
    // Arrange
    final StringBuilder sb = new StringBuilder();
    ScheduledCommand cmd = new ScheduledCommand() {
    
      public void execute() {
        sb.append("triggered!");
      }
    };
    
    // Act
    Scheduler.get().scheduleDeferred(cmd);
    
    // Assert the cmd is not yet triggered
    assertThat(sb.toString()).isEmpty();
  }
}

This unit test is pretty straightfoward. But running it, you'll get a GwtTestException :

com.googlecode.gwt.test.exceptions.GwtTestException: One exception thrown during gwt-test-utils cleanup phase : 
  at com.googlecode.gwt.test.GwtTest.tearDownGwtTest(GwtTest.java:93)
	...
Caused by: com.googlecode.gwt.test.exceptions.GwtTestException: 1 pending scheduledDeferred ScheduledCommand(s) must be triggered manually by calling SampleViewTest.getBrowserSimulator().fireLoopEnd() before making your test assertions
	at com.googlecode.gwt.test.internal.BrowserSimulatorImpl.afterTest(BrowserSimulatorImpl.java:78)
	at com.googlecode.gwt.test.internal.AfterTestCallbackManager.executeCallback(AfterTestCallbackManager.java:95)
	at com.googlecode.gwt.test.internal.AfterTestCallbackManager.triggerCallbacks(AfterTestCallbackManager.java:67)
	at com.googlecode.gwt.test.GwtTest.tearDownGwtTest(GwtTest.java:85)

In fact, your test ran successfully. But after each @Test methods, gwt-test-utils verify there is no pending command command to be executed, and would throw an exception if it find one, telling you it must be triggered manually. Let's see how to do this !

The BrowserSimulator interface

A BrowserSimulator interface is provided to be able to notify each queued command to be executed by simulating the end of an Event Loop.

An instance of this interface is simply returned by calling the protected method getBrowserSimulator() in your GwtTestsubclasses. So let's correct our unit test :

@Test
public void deferredCommandShouldNotBeTriggerSynchronously() {
  // Arrange
  final StringBuilder sb = new StringBuilder();
  ScheduledCommand cmd = new ScheduledCommand() {
  
    public void execute() {
      sb.append("triggered!");
    }
  };
    
  // Assert the cmd is not yet triggered
  assertThat(sb.toString()).isEmpty();
  
  // Act
  Scheduler.get().scheduleDeferred(cmd);
    
  // simulate an event loop end
  getBrowserSimulator().fireLoopEnd();
  
  // Assert
  assertThat(sb.toString()).isEqualTo("triggered!");
}

You should never care about the gwt-test-utils internal implementation of the BrowserSimulatorinterface. But keep in mind it's there every commands and asyncCallbacks are queued. This implementation is used to fire an event loop end automatically whenever it seems relevant, like before and after you trigger an DOM event using Browser utilities.
There should be very few cases where you'll need to use getBrowserSimulator().fireLoopEnd() manually.