blob: 36eaf56660ebee00b2b9a49dd9ff38ce2a34cc28 [file] [log] [blame]
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":http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/testing.html#testcontext-framework 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:
{code}
@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);
}
}
{code}
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:
{code}
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)
{code}
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.
!description-of-illegalstate.jpg!
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:
{code}
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;
}
{code}
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:
{code}
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));
}
{code}
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":http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/beans.html#beans-factory-aware 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.
{code}
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
{code}
So the relevant parts of @MyWebApplication@ type will now look something like the following code snippet:
{code}
@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;
}
}
{code}
For additional clarification of how @MyWebApplication@ now relates to both Wicket and Spring framework here is an according class diagram:
!mywebapp-class-diagramm.jpg!