Pages

Friday, July 26, 2013

Unit Testing of JPA Persistence Layer with Spring Framework

In many cases, an application needs to save and read data from database. It's a common test case that you want the data persistence/access layer works as expected. Running a few JUnit tests seems a common practice, however a dependency to an external database makes such JUnit tests less portable or less stable (imaging the tests can simply fail because database is somehow changed externally). This can cause more headache if a continuous integration is required to run such JUnit tests automatically and periodically.

Fortunately, if you are building such an application using Spring Framework and using JPA as a way to persistent/access your data (or using Spring Data), there are some easy ways to perform such JUnit tests against your data access layer.

The basic idea to implement such tests is to use an in-memory database. Such an in-memory database can be constructed with a set of predefined SQL statements/scripts.

Here's a simple example to illustrate the idea. This example is based on the previous Simple JPA example using Spring Framework.

In that example, we've created a data access object (DAO) for the entity User - UserDao. Here we will create the JUnit test case for the two methods (getNameStartsWith, and save). The test code looks like below:

[UserDaoTest.java]
package com.mytechtip.example.springjpatest;

import static org.junit.Assert.*;

import java.util.Calendar;
import java.util.List;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.annotation.Transactional;

@RunWith(org.springframework.test.context.junit4.SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:beans-test.xml")
@Transactional
public class UserDaoTest {
  
  @Autowired
  UserDao userDao;

  @Before
  public void setUp() throws Exception {
  }

  @After
  public void tearDown() throws Exception {
  }

  @Test
  public void testGetNameStartsWith() {
    
    List<User> list = userDao.getNameStartsWith("J");
    assertEquals(1, list.size());
    User user = list.get(0);
    System.out.println(user.getId()+":"+user.getName() + ":" 
        + user.getDob()+":"+user.getAddress());
    assertEquals("Jack", user.getName());
  }

  @Test
  public void testSave() {
    User u = new User();
    u.setAddress("my address");
    u.setName("John Smith");
    
    Calendar cal = Calendar.getInstance();
    cal.clear();
    cal.set(2000, 0, 1);
    u.setDob(cal.getTime());
    
    userDao.save(u);
    
    List<User> list = userDao.getNameStartsWith("J");
    assertEquals(2, list.size());
    User user = list.get(1);
    System.out.println(user.getId()+":"+user.getName() + ":" 
        + user.getDob()+":"+user.getAddress());
    assertEquals("John Smith", user.getName());
    assertEquals(cal.getTime(), u.getDob());
  }
}

The annotations of the test class plays an important role. It specifies a different beans configuration file ("classpath:beans-test.xml") just for testing. This file should exist in your test source folder as it is only used for unit testing. The content of the beans configuration file is shown as below:

[beans-test.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:p="http://www.springframework.org/schema/p"
  xmlns:tx="http://www.springframework.org/schema/tx" 
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:jdbc="http://www.springframework.org/schema/jdbc"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/tx 
  http://www.springframework.org/schema/tx/spring-tx.xsd
  http://www.springframework.org/schema/context 
  http://www.springframework.org/schema/context/spring-context.xsd
  http://www.springframework.org/schema/jdbc
  http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
  ">

  <context:component-scan base-package="com.mytechtip.example.springjpatest">
  </context:component-scan>

  <tx:annotation-driven />

  <bean class="org.springframework.orm.jpa.JpaTransactionManager"
    id="transactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
  </bean>

  <bean id='entityManagerFactory'
    class='org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean'>
    <property name="persistenceUnitName" value="spring-jpa-test" />
    <property name='dataSource' ref='dataSource' />
    <property name="jpaPropertyMap">
      <map>
        <entry key="eclipselink.weaving" value="false" />
      </map>
    </property>
  </bean>

  <jdbc:embedded-database id="dataSource">
    <jdbc:script location="classpath:create-db.sql" />
    <jdbc:script location="classpath:create-data.sql" />
  </jdbc:embedded-database>
</beans>

Compared with the previous beans configuration file that specifies an actual hsqldb data source, this beans configuration file creates embedded database using two sql files as the data source. The contents of the two sql files are shown as below. They can be edited in accordance with the junit test case mentioned above.

[create-db.sql]
insert into user(id, name, dob) values (1, 'Jack', '1999-12-31');

[create-data.sql]
CREATE TABLE user 
(ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1 INCREMENT BY 1) NOT NULL, 
ADDRESS VARCHAR(255), 
DOB DATE, 
NAME VARCHAR(255), 
PRIMARY KEY (ID));

The folder/file structure of the project is like the following. This is a maven project which has all the dependency defined in the pom.xml. And now you can download this example project from github and try it out by yourself.

No comments:

Post a Comment