What's the best way to structure a Vaadin-based application so that I can use TDD (test-driven development) to create the application? In other words, I don't want to write tests that require a server or browser (or even simulators of those) as those might be too brittle, too slow, or both.
The question Translating GWT MVP Pattern to Vaadin is somewhat related in that I'm looking for the correct pattern to use to make my UI "logic" as testable as possible, but I'm not sure that MVP translates to the world of Vaadin.
I've only just started using Vaadin, and "Can I do TDD with Vaadin?" was my first consideration. I have found (so far anyway) that it is actually quite easy; though I do end up writing a lot of code.
The first thing I had to do was write a number of factory classes. This is so that I can inject mock UI objects into my classes. For example:
public class ButtonFactory {
public Button create() {
return new Button();
}
public Button create(String caption) {
return new Button(caption);
}
public Button create(String caption, Button.ClickListener listener) {
return new Button(caption, listener);
}
}
I then created factories for the main UI components that I needed:
@ApplicationScoped
public class SiteAdminButtonBarFactory implements Serializable {
private static final long serialVersionUID = -462493589568567794L;
private ButtonFactory buttonFactory = null;
private HorizontalLayoutFactory horizontalLayoutFactory = null;
public SiteAdminButtonBarFactory() {}
@Inject
public SiteAdminButtonBarFactory(HorizontalLayoutFactory horizontalLayoutFactory, ButtonFactory buttonFactory) {
this.horizontalLayoutFactory = horizontalLayoutFactory;
this.buttonFactory = buttonFactory;
}
public SiteAdminButtonBar create() {
HorizontalLayout layout = horizontalLayoutFactory.create();
layout.addComponent(addButton());
layout.addComponent(removeButton());
layout.addComponent(editButton());
return new SiteAdminButtonBar(layout);
}
private Button addButton() {
return buttonFactory.create("Add");
}
private Button removeButton() {
return buttonFactory.create("Remove");
}
private Button editButton() {
return buttonFactory.create("Edit");
}
}
The associated test code is:
public class SiteAdminButtonBarFactoryTest {
private HorizontalLayout horizontalLayout = null;
private HorizontalLayoutFactory horizontalLayoutFactory = null;
private Button addButton = null;
private Button removeButton = null;
private Button editButton = null;
private ButtonFactory buttonFactory = null;
private SiteAdminButtonBarFactory siteAdminButtonBarFactory = null;
@Test
public void shouldCreateAHorizontalLayout() throws Exception {
givenWeHaveAFullyConfiguredSiteAdminButtonBarFactory();
SiteAdminButtonBar siteAdminButtonBar = siteAdminButtonBarFactory.create();
assertThat(siteAdminButtonBar, is(notNullValue()));
verify(horizontalLayoutFactory).create();
}
@Test
public void shouldContainAnADDButton() throws Exception {
givenWeHaveAFullyConfiguredSiteAdminButtonBarFactory();
siteAdminButtonBarFactory.create();
verify(buttonFactory).create("Remove");
verify(horizontalLayout).addComponent(removeButton);
}
@Test
public void shouldContainARemoveButton() throws Exception {
givenWeHaveAFullyConfiguredSiteAdminButtonBarFactory();
siteAdminButtonBarFactory.create();
verify(buttonFactory).create("Edit");
verify(horizontalLayout).addComponent(editButton);
}
@Test
public void shouldContainAnEditButton() throws Exception {
givenWeHaveAFullyConfiguredSiteAdminButtonBarFactory();
siteAdminButtonBarFactory.create();
verify(buttonFactory).create("Add");
verify(horizontalLayout).addComponent(addButton);
}
private void givenWeHaveAFullyConfiguredSiteAdminButtonBarFactory() {
horizontalLayout = mock(HorizontalLayout.class);
horizontalLayoutFactory = mock(HorizontalLayoutFactory.class);
when(horizontalLayoutFactory.create()).thenReturn(horizontalLayout);
addButton = mock(Button.class);
removeButton = mock(Button.class);
editButton = mock(Button.class);
buttonFactory = mock(ButtonFactory.class);
when(buttonFactory.create("Add")).thenReturn(addButton);
when(buttonFactory.create("Remove")).thenReturn(removeButton);
when(buttonFactory.create("Edit")).thenReturn(editButton);
siteAdminButtonBarFactory = new SiteAdminButtonBarFactory(horizontalLayoutFactory, buttonFactory);
}
}
I'll admit that at first I had to write the code first and then the test until I figured out how to structure things. Also, I haven't yet got as far as TDDing the event listeners etc (you'll notice that the button have captions, but no action listeners). But I'm getting there!
You can use the karibu-testing framework: https://github.com/mvysny/karibu-testing . It allows you to run and test your app using actual full-blown Vaadin components, so you don't have to use MVP nor custom component factories to construct special UI for testing. Please see the link above on concrete examples and tutorials on how to write and run Vaadin unit tests for your app.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With