Unit testing GWT

by dan on May 15, 2009

Unit testing GWT can be very difficult.  Let me start by saying I don’t consider tests that use com.google.gwt.junit.client.GWTTestCase to be unit tests, I consider them to be integration tests.  This article is about compiling your GWT code into java bytecode and running it just like any other unit test.  Here’s why you’d want to do this:

  1. Your test will execute quickly.
  2. You can use any testing framework you want instead of being forced to use JUnit.
  3. As you’ll see, you can leverage GWT to mock some dependencies for you.

The first thing we’ll be doing is creating our own GWTMockUtilities class.  GWT already comes with one of these, but it isn’t ideal for our needs.  This class allows us to use GWT.create(…); in Java bytecode without exceptions being thrown.  If you were to call this in a class compiled by the Java compiler, you’d get a runtime exception that says this:

          ERROR: GWT.create() is only usable in client code!  It cannot be called,
              for example, from server code.  If you are running a unit test,
              check that your test case extends GWTTestCase and that GWT.create()
              is not called from within an initializer or constructor.

Lies! GWTMockUtilities also prevents this exception from occurring.  Here’s our version of GWTMockUtilities with a minor change.

import com.google.gwt.core.client.GWTBridge;
import com.google.gwt.core.client.GWT;
 
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
 
/**
 * This is almost an exact copy of GWT's com.google.gwt.junit.GWTMockUtilities except it uses our own GWTWidgetBridge
 */
public class GWTMockUtilities {
  public static void disarm() {
    GWTBridge bridge = new GWTWidgetBridge(); /** our change **/
    setGwtBridge(bridge);
  }
 
  public static void restore() {
    setGwtBridge(null);
  }
 
  private static void setGwtBridge(GWTBridge bridge) {
    Class gwtClass = GWT.class;
    Class[] paramTypes = new Class[] {GWTBridge.class};
    Method setBridgeMethod = null;
    try {
      setBridgeMethod = gwtClass.getDeclaredMethod("setBridge", paramTypes);
    } catch (NoSuchMethodException e) {
      throw new RuntimeException(e);
    }
    setBridgeMethod.setAccessible(true);
    try {
      setBridgeMethod.invoke(gwtClass, new Object[] {bridge});
    } catch (IllegalAccessException e) {
      throw new RuntimeException(e);
    } catch (InvocationTargetException e) {
      throw new RuntimeException(e);
    }
  }
}

Why do we need to change this line? Well the normal GWTMockUtilities will change GWT.create(…)  so that it always returns null instead of throwing an UnsupportedOperationException.  Certainly, this is better, but it tends to cause NullPointerExceptions all over the place, especially if you have any logic in your constructors.

Our GWTWidgetBridge returns a mock of the class you tried to create instead of null.  This gets rid of all of those NullPointerExceptions.

import com.google.gwt.core.client.GWTBridge;
import com.google.gwt.dev.About;
import static org.mockito.Mockito.*;
 
/**
 * This is an exact copy of com.google.gwt.junit.GWTDummyBridge except
 * it returns mocked Widgets instead of null's
 **/
public class GWTWidgetBridge extends GWTBridge {
    @Override
    public  T create(Class classLiteral) {
        return (T) mock(classLiteral); /** Mock what we create.  This used to return null. **/
    }
 
    @Override
    public String getVersion() {
        return About.GWT_VERSION_NUM;
    }
 
    @Override
    public boolean isClient() {
        return false;
    }
 
    @Override
    public void log(String s, Throwable throwable) {
        System.out.println(s);
    }
}

As you can see, I used my favorite Java mocking framework, Mockito. But, I assume you can use your favorite Java mocking framework, too (BTW, your favorite Java mocking framework should be Mockito).  Lets look at this in action.  Here’s some code that we want to test:

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.TextBox;
 
import java.util.Set;
 
public class Unit extends Composite {
 
    protected ListBox listBox1;
    protected TextBox textBox1;
    protected ListBox listBox2;
    protected HTML html1;
    protected Button button1;
    protected CustomFlexTable flexTable;
 
    public Unit() {
        initialize();
        compose();
        initWidget(flexTable);
    }
 
    private void initialize() {
        listBox1 = GWT.create(ListBox.class);
        textBox1 = GWT.create(TextBox.class);
        listBox2 = GWT.create(ListBox.class);
        html1 = GWT.create(HTML.class);
        button1 = GWT.create(Button.class);
        flexTable = GWT.create(CustomFlexTable.class);
    }
 
    private void compose() {
        flexTable.setCellSpacing(0); /** Under normal circumstances, this would cause a NPE **/
 
        flexTable.setWidget(0, 0, listBox1);
        flexTable.setWidget(0, 1, textBox1);
        flexTable.setWidget(0, 2, listBox2);
        flexTable.setWidget(0, 3, button1);
        flexTable.setWidget(1, 0, html1);
        flexTable.setColSpan(1, 0, 4);  /** normally would be flexTable.getFlexCellFormatter().setColSpan(1, 0, 4); To be discussed... **/
    }
 
     //and so on...
}

And here’s how we start our unit test (using Testng):

public class MyTest {
    private Unit unit;
    private HTML html1;
    private TextBox textBox1;
    private ListBox listBox1;
    private ListBox listBox2;
 
    @BeforeMethod
    public void setUp() {
        GWTMockUtilities.disarm();  /** Use our GWTMockUtilities **/
 
        unit = new Unit();
        listBox1 = unit.listBox1; /** These have already been mocked for us **/
        textBox1 = unit.textBox1;
        html1 = unit.html1;
        listBox2 = unit.listBox2;
    }
 
    @AfterMethod
    public void tearDown() {
        GWTMockUtilities.restore(); /** Stop using our GWTMockUtilities (integration tests may appreciate this) **/
    }
 
    @Test
    public void testTrue() {
        assert true;  /** Normally, you can't even get this test to pass because setup fails. **/
    }
 
    //other tests...
}

And with that, we can unit test as if we had written plain old java objects. There are some restrictions on this that may cramp your style.  You have to use GWT.create(…) instead of the new keyword.  You may be used to typing new Label(“FooBar”); in your code. With this method, you’d have to change that to:

Label label = GWT.create(Label.class);
label.setText("FooBar");

The other restriction is related to the CustomFlexTable I’ve created.  This is its source:

public class CustomFlexTable extends FlexTable {
 
    public void setColSpan(int row, int column, int colSpan) {
        getFlexCellFormatter().setColSpan(row, column, colSpan);
    }
}

If I don’t use this class, I would get a NullPointerException.  The reason is that the GWTWidgetBridge we created mock’s the FlexTable but it doesn’t stub out the FlexTable’s getFlexCellFormatter() method.  Mockito reacts by returning null for that method and when we call setColSpan(…) on null, we get a NPE.  In Mockito’s defense, FlexTable is violating the Law of Demeter here. You can argue that the API is the problem.  Also, note that this is only a problem when the constructor calls the compose() method. If the client called the compose method instead of the constructor, we could stub the FlexTable’s getFlexCellFormatter() method before we call compose().

{ 6 comments }

Scott Mitchell May 24, 2009 at 4:03 am

Pretty cool Dan, could you get around your issue with the FlexTable (and any other situations like that) by building a backdoor into your GWTWidgetBridge that allows you to manually prep your mocks for certain widgets? That way you could setup the flex table and cell formatter in your unit test and then set it into the widget bridge before calling disarm. Of course, you’d have to figure out the way to say which call to use your custom mock for. My first reaction to that would be to store the mocks in a map keyed by class with the value being a Queue so that the custom mocks would get used in FIFO order. Then you would check the map for the requested class and use the mock from there if it existed, otherwise use the behavior you have described here.

Marco Massenzio May 24, 2009 at 10:45 pm

Very interesting, I’m definitely going to give it a spin… and thanks for introducing me to Mockito!

Kenny MacDermid June 2, 2009 at 10:54 am

Interesting post.

Instead of the CustomFlexTable I extended the GWTWidgetBridge (or GWTMockBridge as I call it), to return mock CellFormatters and ColumnFormatters when HTMLTable.class.isAssignableFrom(classLiteral). If you’d like it send me an email.

Now I’m just trying to figure out if there’s a clean way to determine when a class is a remote service and return the proper async impl class.

Kenny

Christina June 9, 2009 at 5:43 am

Hello DAN,

I did my testcase as you described and it worked well. Now I’m trying to add the test to a TestSuite and the test allways fails with the error message that no test was found. Could you add a short example for this?

Thank you,
Christina

dan June 9, 2009 at 1:59 pm

Hi Christina,

I think you’ll have to show me your code. This sounds like it may be related to your tool more than the example.

Eugen September 17, 2009 at 11:52 pm

Cool article, but I’m having trouble integrating Mockito into my GWT project (I created a module, included the sources but still have lots of compile errors). Is there any info out there about how to set Mockito up into a GWT project? Thanks.

Comments on this entry are closed.