Monday, May 11, 2009

StrutsTestCase, Spring 2.5 and Hibernate

Okay Struts 1 might be "old" technology but we all can´t be working with the latest frameworks. And in it´s defense I actually like the simplicity of Struts 1 - it just works.

Recently I was faced with what I thought would be quite a common scenario - JUnit testing Struts actions in an application that also utilises Spring (for transaction management) and Hibernate with an Open Session In View filter. This presented two problems. The first was how to replicate the Open Session In View filter which I was using for Hibernate. And secondly how to replicate the listener which initialises all the Spring beans. I ended up with a BaseMockStrutsTestCase class which I used in place of MockStrutsTestCase. See below for the complete code.

In regards to the first problem I found a few posts on the Spring forums. And also had a look at the source for org.springframework.orm.hibernate3.support.OpenSessionInViewFilter.

In regards to the second problem I googled around and found Enabling TDD by Integrating Spring and StrutsTestCase by Keith McMillan. This in turn also pointed me to Matt Raible´s site and his appfuse specific BaseMockStrutsTestCase class. This gave me what I need to solve the problem of replicating the problem of initialising the Spring beans.

So now the two issues glued together in one class.


import java.io.File;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.mock.web.MockServletContext;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import servletunit.struts.MockStrutsTestCase;

/**
* This class used as base for testing all struts actions.
*
* 1) Inits Spring Context - normally done by listener on app startup.
* 2) Opens session normally done by open session in view filter.
*
*/

public abstract class BaseMockStrutsTestCase extends MockStrutsTestCase {

protected WebApplicationContext ctx = null;
// open session in view
private Session session;
private SessionFactory sessionFactory;

public BaseMockStrutsTestCase() {
super();
}

public BaseMockStrutsTestCase(String name) {
super(name);
}

protected void setUp() throws Exception {
super.setUp();

// initialize Spring Context
setContextDirectory(new File("web"));
MockServletContext sc = new MockServletContext("");
sc.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM,"WEB-INF/applicationContext.xml");

ServletContextListener contextListener = new ContextLoaderListener();
ServletContextEvent event = new ServletContextEvent(sc);
contextListener.contextInitialized(event);

// magic bridge to make StrutsTestCase aware of Spring's Context
getSession().getServletContext().setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE));

ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(
getSession().getServletContext());

// open session (open session in view)
sessionFactory = (SessionFactory) getSpringBean("sessionFactory");
session = SessionFactoryUtils.getSession(sessionFactory, true);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
}

public void tearDown() throws Exception {
super.tearDown();
// release session (open session in view)
SessionHolder holder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
session = holder.getSession();
TransactionSynchronizationManager.unbindResource(sessionFactory);
SessionFactoryUtils.closeSession(session);

ctx = null;
}

public Object getSpringBean(String beanId) {
return ctx.getBean(beanId);
}




Be very cafefult that you don´t have 2 Spring contexts. This will result in multiple session being opened and a lot of bashing your head against a wall.

There will be 2 Spring contexts if you have the following in your struts-config.xml.







And the following in your web.xml.



org.springframework.web.context.ContextLoaderListener



You should only need to one entry in your web.xml.

No comments:

Post a Comment