GeekLondon.com Help icon Syndication Feed icon 

Spring, Hibernate, Session per Request, and Transactional annotations

Rather than try to create configuration files to wire up a Spring context for my unit tests I like to set up the environment programmatically. This way I am (hopefully) testing the functionality of my units, not the configuration file that I prepared for them. I've certainly had situations where a class worked just dandy in my unit tests but broke horribly in live because I'd got the config wrong for live.

This can all get tricky when you are taking advantage of a lot of the automagic features of Spring contexts, so it makes sense to push a lot of this stuff up into a base test class. Here's the example I'm currently using to unit test some DAOs that are using the @Transactional annotation and autoproxying, the OpenSessionInViewFilter to enforce session-per-request, and Hibernate to manage the entities. I haven't seen this particular combination catered to explicitly anywhere, and it's not particularly weird so I thought it worth putting up for general consumption.

Note that I do use a configuration file to set up the Hibernate entities. I generally use my live hibernate.cfg.xml file here (instead of the test.cfg.xml that I've shown below) because I use Spring to manage the DataSource, but you'll want to use a separate file if your database connection details are managed in the hibernate config. You may also want to use a separate configuration with the hibernate.hbm2ddl.auto property set to create in order to ensure that required tables are available to your test. I'm using a transient in-memory HSQL database to act as a unit test database. Strictly speaking this makes it an integration test, I suppose, but you can't do satisfying unit tests without a "real" database because otherwise you tend to end up verifying that your unit has been written in a particular way, rather than to perform a particular function. Think of HSQL here as being a mock database.

package com.fatmoggy.lola.service;


import javax.sql.DataSource;

import junit.framework.TestCase;

import org.hibernate.Session;
import org.hibernate.SessionFactory;

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate3.HibernateTransactionManager;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.transaction.support.TransactionSynchronizationManager;

abstract public class HibBaseCase extends TestCase{
   protected SessionFactory sessionFactory;
   private TransactionInterceptor interceptor;
   
   @Override
   protected void setUp() throws Exception {
      final DataSource dataSource = new DriverManagerDataSource("org.hsqldb.jdbcDriver","jdbc:hsqldb:mem:test","sa","");
      
      final AnnotationSessionFactoryBean factory = new AnnotationSessionFactoryBean();
      factory.setConfigLocation(new ClassPathResource("test.cfg.xml"));
      factory.setDataSource(dataSource);      
      factory.afterPropertiesSet();
      this.sessionFactory = (SessionFactory)factory.getObject();
      
      final TransactionAttributeSource transactionAttributeSource = new AnnotationTransactionAttributeSource();

      final HibernateTransactionManager txm = new HibernateTransactionManager();
      txm.setSessionFactory(this.sessionFactory);
      txm.afterPropertiesSet();
      
      interceptor = new TransactionInterceptor();
      interceptor.setTransactionAttributeSource(transactionAttributeSource);
      interceptor.setTransactionManager(txm);
      interceptor.afterPropertiesSet();

      final Session session = SessionFactoryUtils.getSession(sessionFactory, true);
      TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
   }

   @Override
   protected void tearDown() throws Exception {
      final SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
      SessionFactoryUtils.closeSession(sessionHolder.getSession());
      sessionFactory.close();
   }

   @SuppressWarnings("unchecked")
   public <T> T createTransactionalProxy(final Class<T> iface, final Object target) {
      final ProxyFactory pf = new ProxyFactory(iface,this.interceptor);
      pf.setTarget(target);
      return (T)pf.getProxy();
   }
}

My derived unit test then generally looks something like this:

public class AppServiceTest extends HibBaseCase {
   private AppService appService;

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

      // Configures my HibernateDaoSupport based DAO implementation
      final AppServiceImpl impl = new AppServiceImpl();
      impl.setSessionFactory(super.sessionFactory);
      impl.afterPropertiesSet();

      // Creates the local DAO for testing wrapped in an annotation-aware transactional proxy
      appService =  super.createTransactionalProxy(AppService.class, impl);
   }
	
   public void testApplicationLifecycle() {
      // ... etc.
   }
}
Posted at Nov 20, 2007 1:53:02 PM, and last updated Nov 20, 2007 2:02:41 PM
Section separator

Add Comments