24. Test Driven Development with Wicket and Spring
Since the development of many web applications is mostly based on the Spring framework for dependency injection and application configuration in general, it’s especially important to get these two frameworks running together smoothly not only when deployed on a running server instance itself but rather during the execution of JUnit based integration tests as well. Thanks to the WicketTester API provided by the Wicket framework itself, one can easily build high-quality web applications while practicing test driven development and providing a decent set of unit and integration tests to be executed with each build. As already mentioned previously, integration and configuration of our web applications is based on a lightweight Spring container meaning that the integration of Spring’s ApplicationContext and a WicketTester API is essential to get our integration tests running. In order to explain how to achieve that integration in an easy and elegant fashion in your integration test environment, we’ll first take a look at a configuration of these 2 framework beauties in a runtime environment.
24.1. Configuration of the runtime environment
In order to get the Wicket framework up to speed when your server is up and running, you usually configure a WicketFilter instance in your web application deployment descriptor file (web.xml) while passing it a single init parameter called applicationClassName that points to your main implementation class extending org.apache.wicket.protocol.http.WebApplication where all of your application-wide settings and initialization requirements are dealt with:
<filter>
<filter-name>wicketfilter</filter-name>
<filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
<init-param>
<param-name>applicationClassName</param-name>
<param-value>com.comsysto.webapp.MyWebApplication</param-value>
</init-param>
</filter>
In case you want to get Wicket application up and running while leaving the application configuration and dependency injection issues to the Spring container, the configuration to be provided within the deployment descriptor looks slightly different though:
<web-app>
<filter>
<filter-name>wicketfilter</filter-name>
<filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
<init-param>
<param-name>applicationFactoryClassName</param-name>
<param-value>org.apache.wicket.spring.SpringWebApplicationFactory</param-value>
</init-param>
</filter>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
</web-app>
The additional configuration part containing listener and context parameter definition is a usual Spring container related configuration detail. ContextLoaderListener is an implementation of standard Servlet API ServletContextListener interface provided by the Spring framework itself and is responsible for looking up an according bean definition file(s) specified by the context param above and creating an ApplicationContext instance during servlet context initialization accordingly. When integrating an ApplicationContext instance with Wicket, one of the beans defined in the above mentioned Spring bean definition file has to be your own specific extension of org.apache.wicket.protocol.http.WebApplication. You can either define a suitable bean in the bean definition file itself:
<beans>
<bean id="myWebApp" class="com.comsysto.webapp.MyWebApplication"/>
</beans>
or use powerful classpath scanning feature of the Spring framework and annotate the MyWebApplication implementation with the appropriate Component annotation accordingly while enabling the Spring container to scan the according package(s) of your application for relevant bean definitions:
<beans>
<context:component-scan base-package="com.comsysto.webapp" />
<context:component-scan base-package="com.comsysto.webapp.service" />
<context:component-scan base-package="com.comsysto.webapp.repository" />
</beans>
Either way, if everything goes well, you’ll get a pre-configured ApplicationContext all set up during the startup of your web container. One of the beans in the ApplicationContext will be your own extension of Wicket’s WebApplication type. SpringWebApplicationFactory implementation provided by the Wicket framework itself that you have defined as the applicationFactoryClassName in the configuration of your WicketFilter will then be used in order to retrieve that very same WebApplication bean out of your Spring ApplicationContext. The Factory expects one and only one extension of Wicket’s very own WebApplication type to be found within the ApplicationContext instance at runtime. If no such bean or more than one bean extending WebApplication is found in the given ApplicationContext an according IllegalStateException will be raised and initialization of your web application will fail:
Map<?,?> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(ac,WebApplication.class, false, false);
if (beans.size() == 0)
{
throw new IllegalStateException("bean of type [" + WebApplication.class.getName() +
"] not found");
}
if (beans.size() > 1)
{
throw new IllegalStateException("more than one bean of type [" +
WebApplication.class.getName() + "] found, must have only one");
}
After the WebApplication bean has been successfully retrieved from the ApplicationContext via SpringWebApplicationFactory, WicketFilter will then, as part of its own initialization process, trigger both internalInit() and init() methods of the WebApplication bean. The latter one is the exact spot where the last piece of the runtime configuration puzzle between Wicket and Spring is to be placed :
@Component
public class MyWebApplication extends WebApplication {
@Override
protected void init() {
super.init();
getComponentInstantiationListeners().add(new SpringComponentInjector(this));
}
}
SpringComponentInjector provided by the Wicket framework enables you to get dependencies from the ApplicationContext directly injected into your Wicket components by simply annotating these with the according SpringBean annotation.
24.2. Configuration of the JUnit based integration test environment
One of the main features of Apache Wicket framework is the ability to easily write and run plain unit tests for your Pages and all other kinds of Components that even include the verification of the rendering process itself by using JUnit framework and the WicketTester API only. When using Spring framework for application configuration together with Wicket, as we do, you can even use the same tools to easily write and run full blown integration tests for your web application as well. All you have to do is use Spring’s TestContext framework additionally to configure and run your JUnit based integration tests. The Spring Framework provides a set of Spring specific annotations that you can use in your integration tests in conjunction with the TestContext framework itself in order to easily configure an according ApplicationContext instance for your tests as well as for appropriate transaction management before, during and after your test execution. Following code snippet represents a simple JUnit 4 based test case using Spring’s specific annotations in order to initialize an ApplicationContext instance prior to executing the test itself:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:WEB-INF/applicationContext.xml"})
@TransactionConfiguration(transactionManager = "txManager", defaultRollback = false)
public class LoginPageTest {
private WicketTester tester;
@Autowired
private ApplicationContext ctx;
@Autowired
private MyWebApplication myWebApplication;
@Before
public void setUp() {
tester = new WicketTester(myWebApplication);
}
@Test
@Transactional
@Rollback(true)
public void testRenderMyPage() {
tester.startPage(LoginPage.class);
tester.assertRenderedPage(LoginPage.class);
tester.assertComponent("login", LoginComponent.class);
}
}
By defining three annotations on the class level (see code snippet above) in your test, Spring’s TestContext framework takes care of preparing and initializing an ApplicationContext instance having all the beans defined in the according Spring context file as well as the transaction management in case your integration test includes some kind of database access. Fields marked with Autowired annotation will be automatically dependency injected as well so that you can easily access and use these for your testing purposes. Since MyWebApplication, which extends Wicket’s WebApplication type and represents the main class of our web application, is also a bean within the ApplicationContext managed by Spring, it will also be provided to us by the test framework itself and can be easily used in order to initialize a WicketTester instance later on during the execution of the test’s setUp() method. With this kind of simple, annotation based test configuration we are able to run an integration test that verifies whether a LoginPage gets started and initialized, whether the rendering of the page runs smoothly and whether the page itself contains a LoginComponent that we possibly need in order to process user’s login successfully.
When you run this test though, you’ll unfortunately get the following exception raised:
java.lang.IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?
at org.springframework.web.context.support.WebApplicationContextUtils.
getRequiredWebApplicationContext(WebApplicationContextUtils.java:84)
at org.apache.wicket.spring.injection.annot.
SpringComponentInjector.<init>(SpringComponentInjector.java:72)
at com.comsysto.serviceplatform.uiwebapp.MyWebApplication.
initializeSpringComponentInjector(MyWebApplication.java:59)
at com.comsysto.serviceplatform.uiwebapp.MyWebApplication.
init(MyWebApplication.java:49)
at org.apache.wicket.protocol.http.WicketFilter.
init(WicketFilter.java:719)
at org.apache.wicket.protocol.http.MockWebApplication.
<init>(MockWebApplication.java:168)
at org.apache.wicket.util.tester.BaseWicketTester.
<init>(BaseWicketTester.java:219)
at org.apache.wicket.util.tester.WicketTester.
<init>(WicketTester.java:325)
at org.apache.wicket.util.tester.WicketTester.
<init>(WicketTester.java:308)
As you can see above, the Exception gets raised during the initialization of the WicketTester instance even before the actual test method gets executed. Even though we have applied rather cool and simple annotation based test configuration already described and passed in perfectly well prepared ApplicationContext instance to the WicketTester instance in the constructor, somewhere down the rabbit hole someone complained that no WebApplicationContext instance could have been found which seems to be required in order to initialize the WicketTester properly.
The problem that we run against here is due to the fact that SpringComponentInjector during its own initialization is trying to get hold of an according Spring’s ApplicationContext instance that would normally be there in a runtime environment but does not find any since we are running in a test environment currently. SpringComponentInjector delegates to Spring’s own WebApplicationContextUtils class to retrieve the instance of ApplicationContext out of the ServletContext which is perfectly fine for a runtime environment but is unfortunately failing in a test environment:
public static WebApplicationContext getRequiredWebApplicationContext(ServletContext sc)
throws IllegalStateException {
WebApplicationContext wac = getWebApplicationContext(sc);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
}
return wac;
}
If you still remember we defined a ContextLoaderListener in our web.xml file as part of the configuration of our runtime environment that makes sure an according WebApplicationContext instance gets initialized and registered against the ServletContext properly. Luckily, this problem can easily be solved if we slightly change the way we initialize SpringComponentInjector in our main MyWebApplication class. Apart from the constructor that we have used so far, there is another constructor in the SpringComponentInjector class that expects the caller to provide it with an according ApplicationContext instance rather than trying to resolve one on its own:
public SpringComponentInjector(WebApplication webapp, ApplicationContext ctx,
boolean wrapInProxies)
{
if (webapp == null)
{
throw new IllegalArgumentException("Argument [[webapp]] cannot be null");
}
if (ctx == null)
{
throw new IllegalArgumentException("Argument [[ctx]] cannot be null");
}
// store context in application's metadata ...
webapp.setMetaData(CONTEXT_KEY, new ApplicationContextHolder(ctx));
// ... and create and register the annotation aware injector
InjectorHolder.setInjector(new AnnotSpringInjector(new ContextLocator(), wrapInProxies));
}
In order to use this constructor instead of the one we used previously, we now obviously need to get hold of the ApplicationContext instance on our own in our MyWebApplication implementation. The easiest way to do this is to use Spring’s own concept of lifecycle callbacks provided to the beans managed by the Spring container. Since our MyWebApplication is also a bean managed by the Spring container at runtime (enabled by the classpath scanning and Component annotation on a type level), we can declare it to implement ApplicationContextAware interface which ensures that it gets provided with the ApplicationContext instance that it runs in by the Spring container itself during startup.
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
So the relevant parts of MyWebApplication type will now look something like the following code snippet:
@Component
public class MyWebApplication extends WebApplication implements ApplicationContextAware {
@Override
protected void init() {
addComponentInstantiationListener(new SpringComponentInjector(this, ctx, true));
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.ctx = applicationContext;
}
}
For additional clarification of how MyWebApplication now relates to both Wicket and Spring framework here is an according class diagram:
24.3. Summary
With the configuration outlined above, no additional modifications are required to the test itself. It’s going to turn green now. This way you can use exactly the same Spring context configuration that you’d use in your runtime environment for running your JUnit based integration tests as well.