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:
- Your test will execute quickly.
- You can use any testing framework you want instead of being forced to use JUnit.
- 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().
{ Comments on this entry are closed }
