I like to share some testing experience with you, as I am very happy
about it:
I have a GWT app that contains some complex business rules/logic which
are difficult to test as they are embedded in the View.
This is something that always frustrated me as bugs very difficult to
track.
Used testing:
1) I use the MVP(C) pattern everywhere possible and make presenter
tests.
2) I use a few GWT tests but they always give me problems as I am
running in -noserver mode.
3) I use Selenium tests for display testing (running all Selenium
tests take more then 30 hours on one machine).
I tried to extract/put this difficult-to-test embedded business logic
(BL) in to a presenter, but things got very ugly and difficult to
maintain. I found out that MVP is great but not always suitable.
## An example of BL that I couldn't test with one of the above
methods:
I have a menu on the left side that show a display on the right side
with text box entries that the user can fill in. The user can add and
remove new entries and erase the content of a line that contains a
collection of entries.
The content of the entries are stored and retrieved from a central
data model. It's retrieved when the display is shown and stored when
the user selects another menu item such that it navigates away from
the current display (not earlier as might want to undo his changes if
they aren't valid for example).
## I want to test the following scenario:
Correct storage of the display value in the data model. That sounds
simple but suppose a member erases some entries and removes some
entries and creates a few new ones. You have to register the removed
entries such that they are removed from the data model when the user
selects another menu item (not earlier).
Testing this with Selenium was hard as these details aren't easy made
visible on the screen. Detail: i had to save the data model to the
backend and restart the application such that the display is filled
with the earlier stored data.
However, it's important to test this as bugs are unacceptable in this
part.
It was bothering me for quite some time now that I couldn't test this
until GWT came out with the IsWidget interface in version 2.1. This
interface is implemented by Widget. It's main use is for View's that
implement IsWidget and can easier be mocked. But it's not really meant
on detail level were I want to use it for.
I was asking for a Widget interface for a long time and finally
IsWidget appeared which was good enough for me...
## My Solution to test the above:
The idea:
Let all widgets implement AsWidget instead of extending of Widget and
create the Display widget as late as possible.
With a few exceptions you could say: the actual creation of the
display widget should only occur when it's attached to some panel.
Changes I made:
In general I never use the GWT panel's directly, but use them trhough
own Panel wrappers such that I can add functionality like insert/
removal animations (I opened a Feature request about this a long time
ago: 3867) .
I changes the add/insert/remove methods in the Panel wrapper to accept
the IsWidget as type and store the IsWidgets widgets in a private
collection.
The contained GWT panel is only used when GWT.isClient yields true.
Side note: because of this private collection, the panel wrapper
returns the IsWidget instance that was added/inserted when requested
with the method gwtWidget(int) instead as with the GWT panel, that
always return the Widget instead, even if you inserted the IsWidget
object that contains the Widget itself. This is confusing (see
http://groups.google.com/group/google-web-toolkit-contributors/browse_thread/thread/e7007d4d4098627b
for discussion about this).
I changed the involved widgets to implement the IsWidget interface and
no longer extend from Widget.
The widget is only created when needed such that the method
IsWidget.asWidget() is called.
The idea is that you embed your view in your presenter (Embedded View
Pattern), see Martin fowler for details about this.
So basically we split up a widget in a Logical and Display widget.
That's basically it. It's pretty cool to finally be able to unit test
(and fast) Widgets or a collection of Widgets, like a new world opens
up :)
Of course you this isn't some kind of Perfect solution, but perfect
solutions don't exists in general, but you can test a lot more then
before.
## Fine tuning:
I fine tuned things a bit:
- A widget will also want to create it self when you set the style,
debug id, etc... on the widget, so you have to fine tune your code
such that it occurs only when the widget is created, or cache these
settings and add them when the widget is created. I choose for the
latter option through a small parent class called SimpleIsWidget that
implements IsWidget that overrides methods like setStyleName,
ensureDebugId, etc.. (add as much as you want). My widgets extends
from SimpleIsWidget and the parent class will cache/buffer style/debug
settings till it's created. It will automatically ask the subclass to
create the display widget when needed or when the method asWidget() is
called. This works pretty well.
- When listening for an event, the event will contain the display
widget that triggered the event. However we often want the logical
widget that created the display widget to perform additional actions
as the display widget doesn't contain any BL.
Example: class Entry that extends SimpleIsWidget, creates widget
EntryDisplay that extends Widget that is contained in the received
event (like a click event).
I added the interface HasCreator that the created display widget can
implement and will return the Logic widget. As such that the Logical
and Display widgets are associated bidirectional.
I hope this helps you with your testing.
I also hope that the GWT dev team will further extend the usage of the
IsWidget interface in the GWT framework such that testing becomes
easier and more testing can be done without the use of the slow
GWTTestCase method. I opened an issue about this some time ago and
hope that developers recognize the need for this and vote the issue
such that the GWT dev team will realize the required changes to gain
better testing.
The issue: 6113 (
http://code.google.com/p/google-web-toolkit/issues/
detail?id=6113)
- Ed