I’ve been using the Vaadin 4 Spring library on my current project, and this has been a very pleasant experience. However, in the middle of the project, a colleague of mine decided to "improve the testability". The intention was laudable, though the project already tried to implement the MVP pattern (please check this article for more detailed information). Instead of correcting the mistakes here and there, he refactored the whole codebase using the provided MVP module… IMHO, this has been a huge mistake. In this article, I’ll try to highlights the stuff that bugs me in the existing implementation, and an alternative solution to it.
The existing MVP implementation consists of a single class. Here it is, abridged for readability purpose:
public abstract class Presenter<V extends View> {
@Autowired
private SpringViewProvider viewProvider;
@Autowired
private EventBus eventBus;
@PostConstruct
protected void init() {
eventBus.subscribe(this);
}
public V getView() {
V result = null;
Class<?> clazz = getClass();
if (clazz.isAnnotationPresent(VaadinPresenter.class)) {
VaadinPresenter vp = clazz.getAnnotation(VaadinPresenter.class);
result = (V) viewProvider.getView(vp.viewName());
}
return result;
}
// Other plumbing code
}
This class is quite opinionated and suffers from the following drawbacks:
- It relies on field auto-wiring, which makes it extremely hard to unit test Presenter classes. As a proof, the provided test class is not a unit test, but an integration test.
- It relies solely on component scanning, which prevents explicit dependency injection.
- It enforces the implementation of the
View
interface, whether required or not. When not using the Navigator, it makes the implementation of an emptyenterView()
method mandatory. - It takes responsibility of creating the View from the view provider.
- It couples the Presenter and the View, with its
@VaadinPresenter
annotation, preventing a single Presenter to handle different View implementations. - It requires to explicitly call the
init()
method of the Presenter, as the@PostConstruct
annotation on a super class is not called when the subclass has one.
I’ve developed an alternative class that tries to address the previous points - and is also simpler:
public abstract class Presenter<T> {
private final T view;
private final EventBus eventBus;
public Presenter(T view, EventBus eventBus) {
Assert.notNull(view);
Assert.notNull(eventBus);
this.view = view;
this.eventBus = eventBus;
eventBus.subscribe(this);
}
// Other plumbing code
}
This class makes every subclass easily unit-testable, as the following snippets proves:
public class FooView extends Label {}
public class FooPresenter extends Presenter<FooView> {
public FooPresenter(FooView view, EventBus eventBus) {
super(view, eventBus);
}
@EventBusListenerMethod
public void onNewCaption(String caption) {
getView().setCaption(caption);
}
}
public class PresenterTest {
private FooPresenter presenter;
private FooView fooView;
private EventBus eventBus;
@Before
public void setUp() {
fooView = new FooView();
eventBus = mock(EventBus.class);
presenter = new FooPresenter(fooView, eventBus);
}
@Test
public void should_manage_underlying_view() {
String message = "anymessagecangohere";
presenter.onNewCaption(message);
assertEquals(message, fooView.getCaption());
}
}
The same Integration Test as for the initial class can also be handled, using explicit dependency injection:
public class ExplicitPresenter extends Presenter<FooView> {
public ExplicitPresenter(FooView view, EventBus eventBus) {
super(view, eventBus);
}
@EventBusListenerMethod
public void onNewCaption(String caption) {
getView().setCaption(caption);
}
}
@Configuration
@EnableVaadin
public class ExplicitConfig {
@Autowired
private EventBus eventBus;
@Bean
@UIScope
public FooView fooView() {
return new FooView();
}
@Bean
@UIScope
public ExplicitPresenter fooPresenter() {
return new ExplicitPresenter(fooView(), eventBus);
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ExplicitConfig.class)
@VaadinAppConfiguration
public class ExplicitPresenterIT {
@Autowired
private ExplicitPresenter explicitPresenter;
@Autowired
private EventBus eventBus;
@Test
public void should_listen_to_message() {
String message = "message_from_explicit";
eventBus.publish(this, message);
assertEquals(message, explicitPresenter.getView().getCaption());
}
}
Last but not least, this alternative also let you use auto-wiring and component-scanning if you feel like it! The only difference being that it enforces constructor auto-wiring instead of field auto-wiring (in my eyes, this counts as a plus, albeit a little more verbose):
@UIScope
@VaadinComponent
public class FooView extends Label {}
@UIScope
@VaadinComponent
public class AutowiredPresenter extends Presenter<FooView> {
@Autowired
public AutowiredPresenter(FooView view, EventBus eventBus) {
super(view, eventBus);
}
@EventBusListenerMethod
public void onNewCaption(String caption) {
getView().setCaption(caption);
}
}
@ComponentScan
@EnableVaadin
public class ScanConfig {}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ScanConfig.class)
@VaadinAppConfiguration
public class AutowiredPresenterIT {
@Autowired
private AutowiredPresenter autowiredPresenter;
@Autowired
private EventBus eventBus;
@Test
public void should_listen_to_message() {
String message = "message_from_autowired";
eventBus.publish(this, message);
assertEquals(message, autowiredPresenter.getView().getCaption());
}
}
The good news is that this module is now part of the vaadin4spring project on Github. If you need MVP for your Vaadin Spring application, you’re just a click away!