Monday, June 22, 2009

Base Test Class with Maven

I've got some base Test classes (with settings for Spring, Wicket, etc) that I want to reuse in my integration tests, and I've put them under a 'test' directory (instead of 'main'), but that means that they don't get built and included in the main artifact. Here's how I forced Maven to build it into a test artifact (in the build/plugins section):


<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>


... and here's how I included it as a dependency in the other projects:


<dependency>
<groupId>com.max.services</groupId>
<artifactId>MaxServices</artifactId>
<version>1.0.2-SNAPSHOT</version>
</dependency>

Base Test Class for Spring

When using Spring to configure my web application, I usually want to employ the same XML configuration file as the application, especially for database access. So here is what I currently use as the base class for all my integration test cases.


public class ServicesTestBase extends AbstractTransactionalDataSourceSpringContextTests {

private static final String CONFIG_LOCATION = "/context/applicationContext.xml";

@Override
protected ConfigurableApplicationContext createApplicationContext(String[] locations) {
ConfigurableWebApplicationContext appContext = new XmlWebApplicationContext();

// this is org.springframework.mock.web.MockServletContext
MockServletContext servletContext = new MockServletContext();
servletContext.addInitParameter(JMXSpringMBeanNamingStrategy.CONFIG_INIT_PARAM_NAME, CONFIG_LOCATION);
appContext.setServletContext(servletContext);

appContext.setConfigLocations(locations);
appContext.refresh();
return appContext;
}

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


In addition to those two methods, earlier projects have overridden the 'onSetupBeforeTransaction' method as follows, although I found I haven't had to do this for my plain old integration tests:


@Override
protected void onSetUpBeforeTransaction() throws Exception {
// this is our own home-grown class which you may not need; source may be available by request
StaticApplicationContext.setContext(this.applicationContext); // Do this BEFORE onSetUpBeforeTransaction()
super.onSetUpBeforeTransaction();
}


However, on my Wicket project, I did have to play around with it a bit, so here is what I created for the 'HomePageTest':


@Override
protected void onSetUpBeforeTransaction() throws Exception {

StaticApplicationContext.setContext(this.applicationContext); // Do this BEFORE onSetUpBeforeTransaction()

super.onSetUpBeforeTransaction();

AuthenticatedWebApplication authenticatedWebApp = new BackOfficeApplication() {
@Override
public void init() {
addComponentInstantiationListener(new SpringComponentInjector(this, HomePageTest.this.applicationContext));
initForAppAndAllTests();
}
};
tester = new WicketTester(authenticatedWebApp);
}


One thing you might have noticed is that JMXSpringMBeanNamingStrategy, so I'll explain real quick: it's to allow me to share the same applicationContext.xml file with all my web app contexts such that the JMX engine won't break due to duplicate names. It's inserted this way:


<!-- (Initially copied from http://static.springframework.org/spring/docs/2.0.x/reference/jmx.html ) -->
<!-- This bean must not be lazily initialized if the exporting is to happen. (comment from docs) -->
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
<property name="namingStrategy"><bean class="com.max.utils.JMXSpringMBeanNamingStrategy" /></property>
<property name="beans">
<map merge="true">
<entry key="com.max.services.forWebApp:name=data-source" value-ref="dataSource" />
<entry key="com.max.services.forWebApp:name=log4j-settings" value-ref="log4jBean" />
<entry key="com.max.services.forWebApp:name=test-settings" value-ref="dynamicTestSettings" />
</map>
</property>
</bean>


Here is how it's defined; you can tell that it's not ideal, but it's what I got:


public class JMXSpringMBeanNamingStrategy implements ObjectNamingStrategy, ServletContextAware {

public static final String CONFIG_INIT_PARAM_NAME = "contextConfigLocation";

private ServletContext context;

public void setServletContext(ServletContext _context) {
this.context = _context;
}

public ObjectName getObjectName(Object managedBean, String beanKey) throws MalformedObjectNameException {
// There's got to be a better way to get the context name of this app!
String springWebAppConfigFile = context.getInitParameter(CONFIG_INIT_PARAM_NAME);
String configName = springWebAppConfigFile;
configName = configName.replace("classpath", "");
configName = configName.replace("webapp", "");
configName = configName.replace("xml", "");
configName = configName.replace("context", "");
configName = configName.replaceAll("[\\:\\/\\.\\-]", "");
String newBeanKey = beanKey.replaceFirst("forWebApp", configName);
return new ObjectName(newBeanKey);
}

}