Showing posts with label DetachedCriteria. Show all posts
Showing posts with label DetachedCriteria. Show all posts

Thursday, June 9, 2011

Unit Testing Hibernate Data Access Objects using JUnit 4 – Part II

In part I we setup the infrastructure or the framework for unit testing. In this part we will write out domain/entity class, dao interface, dao implementation test and then dao implementation. When we write our test we know it will fail because no such method will exist in the dao implementation. However, we will need to create the implementation class, albeit without any methods. So, let's get straight to it.

@Entity
public class Item {

@Id@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;

@ManyToOne
private Order order;
private String product;
private double price;
private int quantity;
/**
* @return the id
*/
public Long getId() {
return id;
}

/**
* @return the order
*/
public Order getOrder() {
return order;
}
// --- getters and setters follow.
//--- override the ToString()and HashCode()

public class Order{

//Other instance variables ....
@OneToMany(cascade=CascadeType.ALL)
@JoinColumn(name="ORDER_ID")
private Collection items = new LinkedHashSet();

/**
* @return the items
*/
public Collection getItems() {
return items;
}
/**
* @param items the items to set
*/
public void setItems(Collection items) {
this.items = items;
}
}


Few things to note here if you are using hibernate are ITEM has many to one relationship with ORDER- an order has many items whereas an item belongs to an order. In the database you will have ORDER_ID column in the ITEM table. Cascade.ALL means whenever ORDER is deleted, corresponding ITEM is set to null. We will not update the id of ORDER because it is auto-generated. So update to an ORDER does not have any bearing on ITEM. The ids are auto-generated.

Now that we have our domain objects, we will write ItemDao. You will similarly write OrderDao, but  I will leave that to you.

public interface ItemDao {

/**
* Given an item id
* return the Item object.
* @param id of the
* @return Matching Item object.
*/
public Item findById(Long itemId);

/**
* @return All items
*/
public List<Item> findAllItems();
}


We have two simple methods to find an Item by item id and findAllItems. You would indeally expand on this and write methods to delete, update, findItemByOrderId and so on. For now, let's keep things simple.

Now we will implement this dao, except we will return null from the implemented methods.

public class ItemDaoImpl implements ItemDao {

public Item findById(Long itemId){
return null;
}

public List<Item> findAllItems() {
return null;
}
}


We can now write our unit test! If you've followed part I, we setup application-context to inject dao implementation. Now we will read the application-context in order to inject that. Also I mentioned that the test methods will be annotated with @Transactional. This is to ensure that when the method returns (void), the transactions within will be rolledback. This is to ensure that our test db will remain unchanged and we can test again and again with same test data. Of course, this also means that you will need to populate your test data. So let's do that first.

Run this query in MySQL and you will have 2 rows in the ITEM table.

INSERT INTO `test_hibernate`.`item` (
`ID` ,
`PRODUCT` ,
`PRICE` ,
`QUANTITY` ,
`ORDER_ID`

)
VALUES (
NULL , 'Sony Headphones', '99.99', '3', NULL

), (
NULL , 'Logitech XZ Mouse', '15.99', '2', NULL

);


For now we don't set ORDER_ID. Now that you have two rows, we can write our tests.

@ContextConfiguration(locations={"classpath:/applicationContext.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class ItemDaoImplTest {

@Autowired
private ItemDaoImpl dao;

@Test
@Transactional
public void testFindById()
{
Item newItem =  dao.findById(1L);
assertTrue(newItem.getProduct().equals("Sony Headphones"));
}

@Test
@Transactional
public void testFindAllItems()
{
List<Item> itemList = dao.findAllItems();
assertTrue(String.valueOf(itemList.size()).equals("2"));
}

If you run this now, your tests will fail. This is because you have not implemented the methods correctly.

So now, we implement those methods from daoImpl.

public Item findById(Long itemId){
DetachedCriteria itemCriteria = DetachedCriteria.forClass(Item.class);
itemCriteria.add(Restrictions.eq(ID_FIELD, itemId));
List<Item> itemList = findByCriteria(itemCriteria);
if(null != itemList && itemList.size() > 0)
return itemList.get(0);
return null;
}

public List<Item> findAllItems() {
DetachedCriteria itemCriteria = DetachedCriteria.forClass(Item.class);
List<Item> itemList = findByCriteria(itemCriteria);
if(null != itemList && itemList.size() > 0)
return itemList;
return null;
}

As I mentioned in part I, I will use DetachedCriteria to query our database. One subtle advantage of doing this is constructing queries is easy (to read and write) and the other major advantage would be since we are going to call this from another module most likely, for example web application's controller via service method in this module, we want the session to be started only when the dao method is called and to be cleaned up as soon as the method returns.

Run the test again, and it will pass. This takes care of testing dao. In a near future I will show you how to write tests for service methods by mocking out the db. I will also point out the advantage of doing this.

Monday, June 6, 2011

Unit Testing Hibernate Data Access Objects using JUnit 4 - Part I

In this article, I want to show you how to write unit tests for your DAOs. You would preferably use an in-memory db instance like HsqlDb but using a test db is perfectly fine since you're going to rollback each db transaction. One thing to remember while doing a DAO unit test is that you'd want to test with the db provider that you are going to use in the live, except you will use a test db instance and not the live db instance. The reason for this is that let's say you are using Hibernate just like I am doing here. You would want to test whether or not the sql queries run against the db using the specific version of hibernate works. Hibernate ships with different versions of driver classes for different db vendors, but for some reason, let's say the encoding of the db you're going to use in the live version does not support certain SQL queries generated by Hibernate. If you do an in-memory HSQLDB test and pass you will most likely think that will work with your specific version of db provider. I just don't think this is accurate enough especially if your queries are complex joins. Again, the rollback feature works for you to take advantage of and regardless of whether you are using an in-memory instance or not, you would still need to populate some data before testing. How else would you test find methods? Another advice I would like to give is to try and use accurate data. I don't mean real-values of credit cards, but data not like "AAAA" in place of a person's name. You may run into various issues later when populating your test db with such data. One such problem I can think of is if your entities are annotated with column specifications such as length and type and you have added data that may not be 100% compatible with that. Another problem is relationships between entities.

Moving on we will have these steps:

1. Pre-requisites
2. Setting up the application context
3. Writing Domain and DAO interface
4. Writing DAO unit tests
5. Writing DAO implementations

I will cover 1 and 2 in this part to have the framework in place. In the next part we will write our domain (just one) and dao interface, dao unit test and then dao implementation. This is a logical order because we would want to test first an then see what we need in order for the test to pass. That 'what we need' will go into our implementation. This is called, as you might have guessed it, Test Driven Approach.

Pre-requisites

* Spring Core library for dependency injection. We are also going to use SpringJunit4ClassRunner for unit testing.
* Hibernate 3.x. We will be using Hibernate's Criteria, specifically, Detached Criteria. For more info on using Criteria look here.
* MySQL db.

You can use Maven to configure all of these. Here's what part of the pom.xml looks like. If you need more help on configuring a maven project please look at my "How to setup a Maven Java Enterprise Application". You can find that under the category "Deployment". Here's the list of artifacts you'll need:

<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>3.3.2.GA</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
<version>3.3.1.GA</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
<version>3.3.0.ga</version>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.6.0.GA</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jcl</artifactId>
<version>1.5.8</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.16</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.framework.version}</version> <!--version 3.0.5.RELEASE -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
</dependency>


Setting up the Application Context

When the test is run, it will scan the application context to inject the dao interface. The implementation of the dao interface will use Hibernate's sessionFactory to run our Hibernate queries. We will also add our single Item domain/entity to use sessionFactory. That object will be directly mapped to the ITEM table of our db. I will not create the table since this is simple enough. Lastly, we will need to use Transactions in order to rollback our unit test methods. For this, we will annotate our test methods as @Transactional. Below is the applicationContext.xml.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xmlns:tx="http://www.springframework.org/schema/tx"     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

<!--  This is where the properties related to datasource are read from -->
<bean id="propertyConfigurer">
<property name="location" value="classpath:hibernate.properties" />
<property name="ignoreUnresolvablePlaceholders" value="false" />
</bean>

<!--  Define dataSource to use -->
<bean id="dataSource">
<property name="driverClassName" value="${hibernate.jdbc.driver}" /> <!-org.gjt.mm.mysql.Driver -->
<property name="url" value="${hibernate.jdbc.url}" />
<property name="username" value="${hibernate.jdbc.user}" />
<property name="password" value="${hibernate.jdbc.password}" />
</bean>

<!--  The sessionFactory will scan the domain objects and their annotated relationships. -->
<bean id="sessionFactory">
<property name="dataSource" ref="dataSource" />
<property name="annotatedClasses">
<list>
<value>com.company.application.core.domain.Item</value>
..............
</list>
</property>
<property name="schemaUpdate" value="true" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.connection.isolation">2</prop>
<prop key="hibernate.bytecode.use_reflection_optimizer">true</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.jdbc.batch_size">20</prop>
<prop key="hibernate.max_fetch_depth">2</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
</props>
</property>
</bean>

<!--  Define Transaction Manager. We will use Hibernate Transaction Manager. -->

<bean id="transactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!--  We will set transactional properties with annotation -->
<tx:annotation-driven />
<bean id="itemDao">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</beans>


One thing you might have noticed is that I could easily annotated my Dao as @Resource, have it scanned and not defined in the xml above. That is perfectly legal. Now we're set to write our domain, dao interface, dao test and dao implementation.