WicketTester and Spring

One thing that's always bugged me about testing my wicket applications is that using WicketTester with a Spring-based Application isn't the most enjoyable process. I have been using Selenium to test my applications and that works sort of OK. Selenium has problems detecting which link I click if there are a lot of similar links and since most links in Wicket encode state/version information, you can also have "link drift" so that even when the link is properly recorded, the versioning information might change in a long running session and then Selenium loses the plot. In addition, Selenium tests aren't so simple to run as part of a Hudson run if you're not running Hudson in a windowed environment. So it's not perfect, but I've used it about as well as I can for a while now. But I really wanted that WicketTester option working for me. So I sat down and figured it out. I'm working with Wicket 1.3 snapshots so you'll need at least beta1 to do it this way. Also, I have some database population code in my application's init to prepopulate certain data elements after initial installation so it complicates the scenario a little but not overly much. Also, I'm using TestNG in this example and a slightly modified/cleaned up version of the testng-spring classes. With that set up out of the way, let's get to some code.

The path to this code was rather frustrating once I sat down to actually figure it out, but the solution turns out to be rather simple. My tests extend AbstractTransactionalSpringContextTests and override two methods:

@Override protected String[] getConfigLocations() { return LOCATIONS; }

@Override protected void prepareTestInstance() throws Exception { inject(); setTransactionManager(transactionManager); }

If you've used spring with either junit or testng, the first method is simple enough. It just lets the test harness know where to find the spring config files. This method is called by the testng classes and all you need to worry about is to properly define that String[] to list your config file(s). In my case, that's

private static final String[] LOCATIONS = { "classpath:applicationContext.xml", "classpath:applicationContext-test.xml" };

The second method, I'll admit, might be a set up issue with my environment, but I had no TransactionManager defined so none of my tests would start a transaction which obviously caused a few problems. That transactionManager field is injected by the testng-spring infrastructure from the same TM used by my application at runtime.

So then, back to the actual test class. I initialize my tests with this:

@BeforeMethod public void init() { tester = new WicketTester(new TestMyApplication()); }

@Override protected ConfigurableApplicationContext loadContextLocations(String[] locations) throws Exception { ++this.loadCount; XmlWebApplicationContext context = new XmlWebApplicationContext(); context.setConfigLocations(locations); context.refresh(); return context; }

private class TestMyApplication extends MyApplication { @Override public void init() { ServletContext servletContext = getServletContext(); XmlWebApplicationContext context = (XmlWebApplicationContext)applicationContext; servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context); context.setServletContext(servletContext); super.init(); } }

I create the WicketTester using a subclasses version of my actual Application. Then I override the testng method to create the application context to make sure the actual implementation matches what wicket-spring will be looking for. The default type is ClassPathXmlApplicationContext which isn't quite what I need. Then I subclass my application and override init() to fetch that application context and add it to the MockServletContext that WicketTester creates. Then I super up to my "real" init() and let things proceed from there.

And that's about it. I use the OpenEntityManagerInView from spring which handles all this for me when I run the application in jetty. But lacking that infrastructure under testng/wickettester, I've had to emulate portions of it and that's what you see here. I just got all this together in the last day or two so there are likely some subtleties missing but it's worked so far for me. Please try this out if you're in a similar situation and if you find missing pieces, please do let me know. I'll keep you updated if I find anything. Enjoy.