Wednesday, August 8, 2007

Unit Testing with Junit, Spring and Hibernate - Part 2

This is a continuation from my previous post on Spring and MockStrutsTestCase and focus on testing Hibernate DAO's. As I mentioned in the previous post, Spring framework provides a nifty base class (AbstractTransactionalDataSourceSpringContextTests) that provides automatic transaction rollback, exposing a JDBC template to interact with the DB and auto wiring of beans.

Lets take a simple DAO class that save a User object to the database:

public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
public void save(User user) {
getHibernateTemplate().save(user);
}
}

Now in order to test this you would write a test class as below extending from AbstractTransactionalDataSourceSpringContextTests class.


public class UserDaoTest extends AbstractTransactionalDataSourceSpringContextTests {
private UserDao userDao;
private SessionFactory sessionFactory = null;

protected String[] getConfigLocations() {
return new String[]{"test-spring-config.xml"};
}

/**
* Spring will automatically inject UserDao object on startup
* @param userDao
*/
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

/**
* Spring will automatically inject the Hibernate session factory on startup
* @param sessionFactory
*/
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}

/**
* Test the save method
*
*/
public void testSave(){
String query = "select count(*) from user where first_name = 'Firstname'";
int count = jdbcTemplate.queryForInt(query);
assertEquals("A user already exists in the DB", 0, count);

User user = new User();
user.setFirstName("Firstname");

userDao.saveUser(user);

// flush the session so we can get the record using JDBC template
SessionFactoryUtils.getSession(sessionFactory, false).flush();

count = jdbcTemplate.queryForInt(query);
assertEquals("User was not found in the DB", 1, count);
}
}

The test class has to implement the protected String[] getConfigLocations() method from the base class and return a String array of Spring config files which will be used to initialize the Spring context.

UserDao and SessionFactory properties are defined with the setter methods and the base class will take care of injecting them automatically from the Spring context. Auto wiring will not work if there are multiple objects implementing the same interface. In such a case you can remove the setter method and retrieve the object using the exposed applicationContext as below.

   /**
* Overridden method from base class which gets called automatically
*/
protected void onSetUpBeforeTransaction() throws Exception {
super.onSetUpBeforeTransaction();
userDao = (UserDao) applicationContext.getBean("userDao");
}

The base class also exposes a JDBC template object (jdbcTemplate) that can be used to query data or setup test data in the database. Note that you need to have a data source and a transaction manager defined in your Spring config in order to use the AbstractTransactionalDataSourceSpringContextTests base class. The data source defined in the config file will be bound to the exposed JDBC template.

In the testSave method first we verify there is no record in the User table where first name equals to 'Firstname' using the jdbc template object. Then we call the save method on the UserDao passing it a User object.

Now we simple verify there is a record in the table where first name equals to 'Firstname'. Before running the query we flush the current Hibernate session to make sure jdbcTemplate can see the newly added record.

Thats it and when the testSave method exits the current transaction will be rolled back and the record inserted to the User table will not be saved. This is great as your test database will always be at a know state at the start and end of a test method.

The spring config file will like below (test-spring-config.xml) :

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
<bean name="userDao" class="com.dao.UserDaoImpl">
<property name="sessionFactory">
<ref bean="sessionFactory"/>
</property>
</bean>

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
<property name="mappingResources">
<list>
<value>hibernates/User.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.query.factory_class">org.hibernate.hql.classic.ClassicQueryTranslatorFactory</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop>
</props>
</property>
</bean>


<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@localhost:1521:ORCL" />
<property name="username" value="test" />
<property name="password" value="test" />
</bean>


<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>

</beans>

Don' forget to click the +1 button below if this post was helpful.

10 comments:

Kelly said...

Very helpful tutorial! Thanks for posting.

Alan said...

this was very helpful.

One quick question. what is
the SessionFactoryUtils class?
It's obviously not in the Spring 2
or Hibernate 3 API. So I guess
it's a class-based singleton
that caches the session object,
right? In particular I'm guessing your class resembles the HibernateUtils class which
the Hibernate tutorial discusses.

Master of Nothing said...

Alan,

SessionFactoryUtils is a Spring class (package org.springframework.orm.hibernate3) and you can see the api documentation here

Alan said...

thanks ... i figured out on my own that i don't need SessionFactoryUtils. I'm attempting to unit test the GenericDao class that comes with the CaveatEmptor app that the Hibernate folks supply, and it comes with a flush() method.

subhas said...

very helpful ... i solved the problem after going thru this tutorial .. thanks

Anuj said...

A very helpful and concise post. It is pretty useful.

Krishna said...

Thanks for sharing the info. It is very much useful:
Java Articles

Nithya said...

Hi,

very helpful tutorial and given some idea to proceed in junit-spring. But i have one doubt. Here when setting the value "FirstName" its not getting stored in DB. But the count is coming as 1. Where the value gets saved. whether some mocking works here? Pls reply...

Anonymous said...

Hi, I have the same setup except that transactions are configured via aop in spring application context. But, my transaction is not being rollbacked after each test case. what can be the possible problem.

Javin @ eclipse remote debugging said...

Indeed very helpful , thanks for sharing information.

Javin
5 tips on writing equals method in Java