Unit testing and integration testing using JUnit, Liquibase, HSQLDB, Hibernate, Maven and Spring Framework

In this article I’d like to show you how to create integration tests using JUnit, Liquibase, HSQLDB , Hibernate, Maven and Spring Framework. We will create maven project, liquibase schema/data deployment to the in-memory database and run integration tests against this database using Spring ORM / Hibernate

It has passed more than 3 years to find a perfect combination to test DAL in the fine-grained manner and isolation. In 2009 I’ve written my first performance review of SQLite (read here). These days I was actually performing unit testing in the background activities (open source and commercial projects). Today I will show you how to achieve much more notable results.

You can download source code here.

I will be using a really huge stack of technologies. So I’ll try to be as much precise as possible. Here is the short plan:

  1. First of all we will create empty maven project
  2. HSQLDB will be set up (server mode) to test schema/data deployment
  3. We will integrate liquibase-maven-plugin
  4. Database schema/data deployment will be created using liqubase and maven
  5. Spring will be added to the project to use Spring ORM / Hibernate / Transactional annotations
  6. Unit tests will be integrated with Spring and  in-memory database (HSQLDB in-memory mode)

Interested? Lets start.

1. Empty maven project

Using your favourite IDE create empty maven project using following pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <artifactId>sandbox</artifactId>
 <groupId>sandbox.app</groupId>
 <version>0.0.1-SNAPSHOT</version>
 <name>sandbox</name>
 <description>Sandbox for testing (outside parent module)</description>
 <properties>
   <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 </properties>
 <dependencies>
 </dependencies>
</project>

2. HSQLDB in server mode

You can download database here. I’ll be using hsqldb-2.2.9.zip in this article. Unpack archive and navigate to [HSQLDB_HOME]\hsqldb\bin and create sandboxDb.bat file (or shell script if you’re *Unix user) with the following contents:

java -cp ../lib/hsqldb.jar org.hsqldb.server.Server –database.0 file:sandboxDb –dbname.0 sandboxDb

After running sandboxDb.bat you should see something like:

To doublecheck that everything is fine insert test data using DatabaseManager:

java -classpath .;../lib/hsqldb.jar org.hsqldb.util.DatabaseManager  -user sa -url jdbc:hsqldb:database/sandboxDb

You should see GUI window (also you can deploy test data using “Options -> Insert Test Data”):

3. Liquibase integration

Now its time to integrate Liquibase into our maven project.

Create liquibase.properties file and put it into sandbox/src/main/resources/liquibase folder. This properties file will store information about our sandboxDb:

#liquibase.properties
driver: org.hsqldb.jdbc.JDBCDriver
url: jdbc:hsqldb:hsql://localhost/sandboxDb
username: SA
password:

Next we’ll need to add dependencies and liquibase-maven-plugin into pom.xml:

	<dependencies>
		<dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<version>2.2.8</version>
		</dependency>
		<dependency>
			<groupId>org.liquibase</groupId>
			<artifactId>liquibase-core</artifactId>
			<version>2.0.5</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.liquibase</groupId>
				<artifactId>liquibase-maven-plugin</artifactId>
				<version>2.0.5</version>
				<configuration>
					<propertyFile>src/main/resources/liquibase/liquibase.properties</propertyFile>
					<changeLogFile>src/main/resources/liquibase/master.xml</changeLogFile>
				</configuration>
			</plugin>
		</plugins>
	</build>

4. Database schema / data deployment

As you can see from the plugin’s configuration (pom.xml) liquibase will be using master.xml database changelog. Let’s create it and put into corresponding folder:

<?xml version="1.0" encoding="UTF-8"?>

<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog

http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd">

	<changeSet id="schema_user_management" author="tillias">

		<comment>User management</comment>

		<createTable tableName="users">
			<column name="id" autoIncrement="true" type="int">
				<constraints primaryKey="true" nullable="false"
					primaryKeyName="pk_users" />
			</column>
			<column name="first_name" type="varchar(128)"></column>
			<column name="last_name" type="varchar(128)"></column>
			<column name="is_disabled" type="boolean" defaultValueBoolean="false">
				<constraints nullable="false" />
			</column>
			<column name="is_deleted" type="boolean" defaultValueBoolean="false">
				<constraints nullable="false" />
			</column>
		</createTable>

		<createTable tableName="roles">
			<column name="id" autoIncrement="false" type="int">
				<constraints primaryKey="true" primaryKeyName="pk_role" />
			</column>
			<column name="name" type="varchar(64)">
				<constraints nullable="false" unique="true" />
			</column>
			<column name="description" type="varchar(256)"></column>
		</createTable>

		<createTable tableName="users_roles">
			<column name="user_id" type="int">
				<constraints foreignKeyName="fk_users_roles_users"
					references="users(id)" nullable="false" />
			</column>
			<column name="role_id" type="int">
				<constraints foreignKeyName="fk_users_roles_roles"
					references="roles(id)" nullable="false" />
			</column>
		</createTable>
	</changeSet>
</databaseChangeLog>

This database changelog containst one changeset with three tables: users, roles, users_roles. Here is the schema:

Let’s test how maven and liquibase will work together. Go to the root directory of our maven project and type:

C:\…\sandbox>mvn liquibase:update -Dliquibase.dropFirst=true

You should see:

Let’s also double check that schema has been deployed. I’ll be using Eclipse Database Development Perspective (using jdbc:hsqldb:hsql://localhost/sandboxDb URL):

Finally lets add some data using separate changeset inside master.xml:

	<changeSet id="data_user_management" author="tillias">
		<insert tableName="roles">
			<column name="id">1</column>
			<column name="name">Guest</column>
		</insert>
		<insert tableName="roles">
			<column name="id">2</column>
			<column name="name">User</column>
		</insert>
		<insert tableName="roles">
			<column name="id">3</column>
			<column name="name">Admin</column>
		</insert>

		<insert tableName="users">
			<column name="id">1</column>
			<column name="first_name">John</column>
			<column name="last_name">Doe</column>
		</insert>
				<insert tableName="users">
			<column name="id">2</column>
			<column name="first_name">James</column>
			<column name="last_name">Brown</column>
		</insert>
		<insert tableName="users">
			<column name="id">3</column>
			<column name="first_name">Andrew</column>
			<column name="last_name">Green</column>
		</insert>

		<insert tableName="users_roles">
			<column name="user_id">1</column>
			<column name="role_id">1</column>
		</insert>
		<insert tableName="users_roles">
			<column name="user_id">2</column>
			<column name="role_id">2</column>
		</insert>
		<insert tableName="users_roles">
			<column name="user_id">3</column>
			<column name="role_id">3</column>
		</insert>
	</changeSet>

If you run liquibase:update once again data will be also inserted into database:

5. Spring integration and declarative transactions

Now let’s integrate into our application Spring ORM, Hibernate and declarative transactions. First of all let’s add spring framework dependencies into pom.xml:

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<spring.version>3.1.1.RELEASE</spring.version>
	</properties>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.10</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.1</version>
		</dependency>

...
		<!-- Spring Framework and Hibernate -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.7.0</version>
		</dependency>
		<dependency>
			<groupId>javassist</groupId>
			<artifactId>javassist</artifactId>
			<version>3.12.1.GA</version>
		</dependency>
		<dependency>
			<groupId>cglib</groupId>
			<artifactId>cglib</artifactId>
			<version>2.2.2</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate</artifactId>
			<version>3.5.4-Final</version>
			<type>pom</type>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-annotations</artifactId>
			<version>3.5.4-Final</version>
		</dependency>
		<dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<version>2.2.8</version>
		</dependency>
		<dependency>
			<groupId>commons-dbcp</groupId>
			<artifactId>commons-dbcp</artifactId>
			<version>1.4</version>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.17</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.1</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.1</version>
		</dependency>

Now let’s add simple applicationContext.xml and put it into src/main/resources:

<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"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.1.xsd


http://www.springframework.org/schema/tx


http://www.springframework.org/schema/tx/spring-tx-3.1.xsd


http://www.springframework.org/schema/context


http://www.springframework.org/schema/context/spring-context-3.1.xsd

						http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">

	<context:component-scan base-package="app" />
	<context:annotation-config />

	<tx:annotation-driven transaction-manager="transactionManager" />
	<aop:config proxy-target-class="true" />
</beans>

Next we will create simple console application to test Spring. Here is the structure of the project:

Here is the source code for app.dal package:

package app.dal;

public interface DataPovider {
	String loadData();
}

@Repository
public class DataProviderImpl implements DataPovider {

	@Override
	public String loadData() {
		System.out.println(String.format("%s has loaded data.", DataProviderImpl.class));

		return "Some data from data provider";
	}

}

This package contains stub for DataProvider. We’ll improve it during Hibernate integration later.  Another package is app.services:

package app.services;

public interface BusinessService {
	String getValue();
}

@Service
public class BusinessServiceImpl implements BusinessService {

	@Autowired
	private DataPovider dataProvider;

	@Override
	public String getValue() {

		String data = dataProvider.loadData();

		System.out.println(String.format("This is %s. DataProvider class is %s",
				BusinessServiceImpl.class, dataProvider.getClass()));

		//perform business operation(s) with data

		return data;
	}
}

This package stores stub for BusinessService to emulate some business logic. Finally here is the source code of app package:

package app;

@Component
public class AppWorker {

	@Autowired
	private BusinessService service;

	public void doWork() {
		System.out.println(String.format("%s started work", AppWorker.class));

		String value = service.getValue();

		System.out.println(String.format("Work value is [%s]", value));
	}
}

public class App {

	private static ApplicationContext context;

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		context = new ClassPathXmlApplicationContext("applicationContext.xml");

		AppWorker worker = context.getBean(AppWorker.class);
		worker.doWork();
	}
}

If we run App.java there will be produced following output to the console (I won’t integrate log4j in this article to keep things simple):

Oct 31, 2012 12:19:57 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@766e3d60: startup date [Wed Oct 31 12:19:57 MSK 2012]; root of context hierarchy

Oct 31, 2012 12:19:57 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [applicationContext.xml]

Oct 31, 2012 12:19:57 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@39e4853f: defining beans [appWorker,dataProviderImpl,businessServiceImpl,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalPersistenceAnnotationProcessor,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0,org.springframework.transaction.interceptor.TransactionInterceptor#0,org.springframework.transaction.config.internalTransactionAdvisor,org.springframework.context.annotation.ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor#0]; root of factory hierarchy

class app.AppWorker started work
class app.dal.DataProviderImpl has loaded data.
This is class app.services.BusinessServiceImpl. DataProvider class is class app.dal.DataProviderImpl
Work value is [Some data from data provider]

At this point we’re ready with simple console application with Spring Framework integrated. Lets continue and integrate Hibernate framework.  Let’s generate data entities first (for example using Hibernate Tools for Eclipse). Entities below are put app.dal.entities package. Constructors, imports and setters are omitted.

Roles data entity:

package app.dal.entities;

@Entity
@Table(name = "ROLES", schema = "PUBLIC", catalog = "PUBLIC", uniqueConstraints = @UniqueConstraint(columnNames = "NAME"))
public class Roles implements java.io.Serializable {
	private int id;
	private String name;
	private String description;
	private Set<UsersRoles> usersRoleses = new HashSet<UsersRoles>(0);

	@Id
	@Column(name = "ID", unique = true, nullable = false)
	public int getId() {
		return this.id;
	}

	@Column(name = "NAME", unique = true, nullable = false, length = 64)
	public String getName() {
		return this.name;
	}

	@Column(name = "DESCRIPTION", length = 256)
	public String getDescription() {
		return this.description;
	}

	@OneToMany(fetch = FetchType.LAZY, mappedBy = "roles")
	public Set<UsersRoles> getUsersRoleses() {
		return this.usersRoleses;
	}
}

Users data entity:

package app.dal.entities;

@Entity
@Table(name = "USERS", schema = "PUBLIC", catalog = "PUBLIC")
public class Users implements java.io.Serializable {

	private Integer id;
	private String firstName;
	private String lastName;
	private boolean isDisabled;
	private boolean isDeleted;
	private Set<UsersRoles> usersRoleses = new HashSet<UsersRoles>(0);

	@Id
	@GeneratedValue(strategy = IDENTITY)
	@Column(name = "ID", unique = true, nullable = false)
	public Integer getId() {
		return this.id;
	}

	@Column(name = "FIRST_NAME", length = 128)
	public String getFirstName() {
		return this.firstName;
	}

	@Column(name = "LAST_NAME", length = 128)
	public String getLastName() {
		return this.lastName;
	}

	@Column(name = "IS_DISABLED", nullable = false)
	public boolean isIsDisabled() {
		return this.isDisabled;
	}

	@Column(name = "IS_DELETED", nullable = false)
	public boolean isIsDeleted() {
		return this.isDeleted;
	}

	@OneToMany(fetch = FetchType.LAZY, mappedBy = "users")
	public Set<UsersRoles> getUsersRoleses() {
		return this.usersRoleses;
	}
}

UsersRolesId data entity:

package app.dal.entities;

@Embeddable
public class UsersRolesId implements java.io.Serializable {

	private int userId;
	private int roleId;

	@Column(name = "USER_ID", nullable = false)
	public int getUserId() {
		return this.userId;
	}

	@Column(name = "ROLE_ID", nullable = false)
	public int getRoleId() {
		return this.roleId;
	}

	public boolean equals(Object other) {
		if ((this == other))
			return true;
		if ((other == null))
			return false;
		if (!(other instanceof UsersRolesId))
			return false;
		UsersRolesId castOther = (UsersRolesId) other;

		return (this.getUserId() == castOther.getUserId())
				&& (this.getRoleId() == castOther.getRoleId());
	}

	public int hashCode() {
		int result = 17;

		result = 37 * result + this.getUserId();
		result = 37 * result + this.getRoleId();
		return result;
	}

}

UsersRoles data entity:

package app.dal.entities;

@Entity
@Table(name = "USERS_ROLES", schema = "PUBLIC", catalog = "PUBLIC")
public class UsersRoles implements java.io.Serializable {
	private UsersRolesId id;
	private Users users;
	private Roles roles;

	@EmbeddedId
	@AttributeOverrides({
			@AttributeOverride(name = "userId", column = @Column(name = "USER_ID", nullable = false)),
			@AttributeOverride(name = "roleId", column = @Column(name = "ROLE_ID", nullable = false)) })
	public UsersRolesId getId() {
		return this.id;
	}

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "USER_ID", nullable = false, insertable = false, updatable = false)
	public Users getUsers() {
		return this.users;
	}

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "ROLE_ID", nullable = false, insertable = false, updatable = false)
	public Roles getRoles() {
		return this.roles;
	}

Now we’re ready to integrate Spring ORM and Hibernate as well as declarative transactions support. Let’s modify applicationContext.xml:

	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close">
		<property name="driverClassName" value="org.hsqldb.jdbc.JDBCDriver" />
		<property name="url" value="jdbc:hsqldb:hsql://localhost/sandboxDb" />
		<property name="username" value="SA" />
		<property name="password" value="" />
	</bean>

	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="packagesToScan" value="app.dal.entities" />
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.connection.pool_size">10</prop>
				<prop key="hibernate.connection.show_sql">true</prop>
				<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
				<prop key="hibernate.show_sql">true</prop>
			</props>
		</property>
	</bean>

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

Next we’ll create log4j.xml and put it into src/main/resources directory.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

	<appender name="ASYNC" class="org.apache.log4j.AsyncAppender">
		<appender-ref ref="CONSOLE" />
	</appender>

	<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
		<layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="%-d: %-5p [%8c](%F:%L) %x - %m%n" />
		</layout>
	</appender>

	<category name="org.hibernate">
		<priority value="info" />
	</category>

	<category name="org.springframework">
		<priority value="error" />
	</category>

	<root>
		<priority value="INFO" />
		<appender-ref ref="ASYNC" />
	</root>

</log4j:configuration>

If everything is done correctly then you’ll see following output during application startup:

2012-10-31 14:16:47,647: INFO  [org.hibernate.cfg.annotations.Version](?:?)  - Hibernate Annotations 3.5.4-Final
2012-10-31 14:16:47,685: INFO  [org.hibernate.cfg.Environment](?:?)  - Hibernate 3.5.4-Final
2012-10-31 14:16:47,687: INFO  [org.hibernate.cfg.Environment](?:?)  - hibernate.properties not found
2012-10-31 14:16:47,691: INFO  [org.hibernate.cfg.Environment](?:?)  - Bytecode provider name : javassist
2012-10-31 14:16:47,694: INFO  [org.hibernate.cfg.Environment](?:?)  - using JDK 1.4 java.sql.Timestamp handling
2012-10-31 14:16:47,776: INFO  [org.hibernate.annotations.common.Version](?:?)  - Hibernate Commons Annotations 3.2.0.Final
2012-10-31 14:16:47,835: INFO  [org.hibernate.cfg.AnnotationBinder](?:?)  - Binding entity from annotated class: app.dal.entities.Roles
2012-10-31 14:16:47,864: INFO  [org.hibernate.cfg.annotations.EntityBinder](?:?)  - Bind entity app.dal.entities.Roles on table ROLES
2012-10-31 14:16:47,915: INFO  [org.hibernate.cfg.AnnotationBinder](?:?)  - Binding entity from annotated class: app.dal.entities.Users
2012-10-31 14:16:47,915: INFO  [org.hibernate.cfg.annotations.EntityBinder](?:?)  - Bind entity app.dal.entities.Users on table USERS
2012-10-31 14:16:47,922: INFO  [org.hibernate.cfg.AnnotationBinder](?:?)  - Binding entity from annotated class: app.dal.entities.UsersRoles
2012-10-31 14:16:47,922: INFO  [org.hibernate.cfg.annotations.EntityBinder](?:?)  - Bind entity app.dal.entities.UsersRoles on table USERS_ROLES
2012-10-31 14:16:47,974: INFO  [org.hibernate.cfg.annotations.CollectionBinder](?:?)  - Mapping collection: app.dal.entities.Roles.usersRoleses -> USERS_ROLES
2012-10-31 14:16:47,976: INFO  [org.hibernate.cfg.annotations.CollectionBinder](?:?)  - Mapping collection: app.dal.entities.Users.usersRoleses -> USERS_ROLES
2012-10-31 14:16:47,982: INFO  [org.hibernate.cfg.AnnotationConfiguration](?:?)  - Hibernate Validator not found: ignoring
2012-10-31 14:16:47,985: INFO  [org.hibernate.cfg.search.HibernateSearchEventListenerRegister](?:?)  - Unable to find org.hibernate.search.event.FullTextIndexEventListener on the classpath. Hibernate Search is not enabled.
2012-10-31 14:16:48,007: INFO  [org.hibernate.connection.ConnectionProviderFactory](?:?)  - Initializing connection provider: org.springframework.orm.hibernate3.LocalDataSourceConnectionProvider
2012-10-31 14:16:48,317: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - RDBMS: HSQL Database Engine, version: 2.2.9
2012-10-31 14:16:48,317: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - JDBC driver: HSQL Database Engine Driver, version: 2.2.8
2012-10-31 14:16:48,351: INFO  [org.hibernate.dialect.Dialect](?:?)  - Using dialect: org.hibernate.dialect.HSQLDialect
2012-10-31 14:16:48,360: INFO  [org.hibernate.transaction.TransactionFactoryFactory](?:?)  - Transaction strategy: org.springframework.orm.hibernate3.SpringTransactionFactory
2012-10-31 14:16:48,361: INFO  [org.hibernate.transaction.TransactionManagerLookupFactory](?:?)  - No TransactionManagerLookup configured (in JTA environment, use of read-write or transactional second-level cache is not recommended)
2012-10-31 14:16:48,361: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Automatic flush during beforeCompletion(): disabled
2012-10-31 14:16:48,361: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Automatic session close at end of transaction: disabled
2012-10-31 14:16:48,361: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - JDBC batch size: 15
2012-10-31 14:16:48,361: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - JDBC batch updates for versioned data: disabled
2012-10-31 14:16:48,361: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Scrollable result sets: enabled
2012-10-31 14:16:48,361: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - JDBC3 getGeneratedKeys(): enabled
2012-10-31 14:16:48,361: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Connection release mode: auto
2012-10-31 14:16:48,362: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Default batch fetch size: 1
2012-10-31 14:16:48,362: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Generate SQL with comments: disabled
2012-10-31 14:16:48,362: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Order SQL updates by primary key: disabled
2012-10-31 14:16:48,362: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Order SQL inserts for batching: disabled
2012-10-31 14:16:48,362: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Query translator: org.hibernate.hql.ast.ASTQueryTranslatorFactory
2012-10-31 14:16:48,363: INFO  [org.hibernate.hql.ast.ASTQueryTranslatorFactory](?:?)  - Using ASTQueryTranslatorFactory
2012-10-31 14:16:48,363: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Query language substitutions: {}
2012-10-31 14:16:48,363: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - JPA-QL strict compliance: disabled
2012-10-31 14:16:48,363: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Second-level cache: enabled
2012-10-31 14:16:48,363: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Query cache: disabled
2012-10-31 14:16:48,363: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Cache region factory : org.hibernate.cache.impl.NoCachingRegionFactory
2012-10-31 14:16:48,364: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Optimize cache for minimal puts: disabled
2012-10-31 14:16:48,364: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Structured second-level cache entries: disabled
2012-10-31 14:16:48,367: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Echoing all SQL to stdout
2012-10-31 14:16:48,367: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Statistics: disabled
2012-10-31 14:16:48,367: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Deleted entity synthetic identifier rollback: disabled
2012-10-31 14:16:48,367: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Default entity-mode: pojo
2012-10-31 14:16:48,367: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Named query checking : enabled
2012-10-31 14:16:48,367: INFO  [org.hibernate.cfg.SettingsFactory](?:?)  - Check Nullability in Core (should be disabled when Bean Validation is on): enabled
2012-10-31 14:16:48,393: INFO  [org.hibernate.impl.SessionFactoryImpl](?:?)  - building session factory
2012-10-31 14:16:48,544: INFO  [org.hibernate.impl.SessionFactoryObjectFactory](?:?)  - Not binding factory to JNDI, no JNDI name configured
class app.AppWorker started work
class app.dal.DataProviderImpl has loaded data.
This is class app.services.BusinessServiceImpl. DataProvider class is class app.dal.DataProviderImpl
Work value is [Some data from data provider]

Let’s improve our DataProvider concrete implementation:

public interface DataPovider {
	String loadData();
	List<Users> getUsers();
}

@Repository
public class DataProviderImpl implements DataPovider {

	@Autowired
	private SessionFactory sessionFactory;

	@Override
	public String loadData() {
		System.out.println(String.format("%s has loaded data.", DataProviderImpl.class));

		return "Some data from data provider";
	}

	@Override
	@Transactional
	public List<Users> getUsers() {
		Session session = sessionFactory.getCurrentSession();
		Query query = session.createQuery("from Users u");
		return query.list();
	}

}

As you can see we’ve added getUsers() method which loads list of Users data entities. SessionFactory is injected by Spring ORM infrastructure.

Let’s change BusinessServiceImpl:

@Service
public class BusinessServiceImpl implements BusinessService {

	@Autowired
	private DataPovider dataProvider;

	@Override
	public String getValue() {
		List<Users> users = dataProvider.getUsers();

		StringBuilder sb = new StringBuilder();
		for (Users u : users) {
			sb.append(String.format("ID=%s, FirstName=%s, LastName=%s\n", u.getId(),
					u.getFirstName(), u.getLastName()));
		}
		return sb.toString();
	}
}

If you run application once again you’ll see following output:

class app.AppWorker started work

Hibernate: select users0_.ID as ID1_, users0_.FIRST_NAME as FIRST2_1_, users0_.IS_DELETED as IS3_1_, users0_.IS_DISABLED as IS4_1_, users0_.LAST_NAME as LAST5_1_ from PUBLIC.PUBLIC.USERS users0_

Work value is [ID=1, FirstName=John, LastName=Doe
ID=2, FirstName=James, LastName=Brown
ID=3, FirstName=Andrew, LastName=Green

6. Writing unit tests / integration tests

Our main goal is to test BusinessServiceImpl – concrete implementation BusinessService. First of all let’s create testApplicationContext.xml and put it into src/test/resources:

<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"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.1.xsd


http://www.springframework.org/schema/tx


http://www.springframework.org/schema/tx/spring-tx-3.1.xsd


http://www.springframework.org/schema/context


http://www.springframework.org/schema/context/spring-context-3.1.xsd

						http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">

	<context:component-scan base-package="app" />
	<context:annotation-config />

	<tx:annotation-driven transaction-manager="transactionManager" />
	<aop:config proxy-target-class="true" />

	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close">
		<property name="driverClassName" value="org.hsqldb.jdbc.JDBCDriver" />
		<property name="url" value="jdbc:hsqldb:mem:testdb;shutdown=false" />
		<property name="username" value="SA" />
		<property name="password" value="" />
	</bean>

	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="packagesToScan" value="app.dal.entities" />
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.connection.pool_size">10</prop>
				<prop key="hibernate.connection.show_sql">true</prop>
				<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
				<prop key="hibernate.show_sql">true</prop>
			</props>
		</property>
	</bean>

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

Please note that we’re using BasicDataSource with jdbc:hsqldb:mem:testdb;shutdown=false. This effectively means that we’ll be using HSQLDB In-Memory mode. Moreover shutdown=false guarantees that in-memory database won’t be destroyed after all connections to the database will have been closed.

Now it’s time to add some magic. Let’s create HsqlDatabase class:

package utils;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import liquibase.Liquibase;
import liquibase.database.jvm.HsqlConnection;
import liquibase.logging.LogFactory;
import liquibase.resource.FileSystemResourceAccessor;
import liquibase.resource.ResourceAccessor;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HsqlDatabase {
	private static final String CHANGE_LOG = "src/main/resources/liquibase/master.xml";
	private static final String CONNECTION_STRING = "jdbc:hsqldb:mem:testdb;shutdown=false";
	private static final String USER_NAME = "SA";
	private static final String PASSWORD = StringUtils.EMPTY;
	private static final Logger LOG = LoggerFactory.getLogger(HsqlDatabase.class);

	private Connection holdingConnection;
	private Liquibase liquibase;

	public void setUp(String contexts) {
		try {
			ResourceAccessor resourceAccessor = new FileSystemResourceAccessor();
			Class.forName("org.hsqldb.jdbcDriver");

			holdingConnection = getConnectionImpl();
			HsqlConnection hsconn = new HsqlConnection(holdingConnection);
			LogFactory.getLogger().setLogLevel("severe");
			liquibase = new Liquibase(CHANGE_LOG, resourceAccessor, hsconn);
			liquibase.dropAll();
			liquibase.update(contexts);
			hsconn.close();
		} catch (Exception ex) {
			LOG.error("Error during database initialization", ex);
			throw new RuntimeException("Error during database initialization", ex);
		}
	}

	private Connection getConnectionImpl() throws SQLException {
		return DriverManager.getConnection(CONNECTION_STRING, USER_NAME, PASSWORD);
	}
}

This class is responsible for the invocation of Liqubase from the source-code of the unit/integration test(s). It uses the same JDBC URL as we put the testApplicationContext.xml:

private static final String CONNECTION_STRING = "jdbc:hsqldb:mem:testdb;shutdown=false";

The same databaseChangeLog will be used for unit/integration test(s) as for the production database schema and data:

private static final String CHANGE_LOG = "src/main/resources/liquibase/master.xml";

So we need to modify our master.xml to support both scenarios (test / prod):

<databaseChangeLog>
	...
	<changeSet id="data_user_management" author="tillias" context="dev">
	...
	</changeSet>

	<include file="src/main/resources/liquibase/testData.xml" />
</databaseChangeLog>

As you can see we’ve added new context=”dev” attribute for the data_user_management changeset. We’ve also included new changelog from testData.xml:

<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog

http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd">

	<changeSet id="test_data_user_management" author="tillias" context="test">
		<insert tableName="users">
			<column name="id">1001</column>
			<column name="first_name">John_Test</column>
			<column name="last_name">Doe_Test</column>
		</insert>
		<insert tableName="users">
			<column name="id">1002</column>
			<column name="first_name">James_Test</column>
			<column name="last_name">Brown_Test</column>
		</insert>
		<insert tableName="users">
			<column name="id">1003</column>
			<column name="first_name">Andrew_Test</column>
			<column name="last_name">Green_Test</column>
		</insert>
	</changeSet>

</databaseChangeLog>

Please note that test_data_user_management changeset is associated with context=”test”.

Up for now we’ve got 3 changesets:

  1. schema_user_management (no contexts specified, will be deployed by liquibase always)
  2. data_user_management (dev context)
  3. test_data_user_management (test context)

For development activities we will use dev context using liquibase-maven-plugin:

mvn liquibase:update -Dliquibase.dropFirst=true -Dliquibase.contexts="dev"

This command will deploy schema_user_management and data_user_management changesets.

For unit/integration test(s) we’ll use test context (passed to the setUp() method of the instance of HsqlDatabase utility class):

package app.services;

import junit.framework.Assert;

import org.junit.BeforeClass;
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.test.context.junit4.SpringJUnit4ClassRunner;

import utils.HsqlDatabase;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:testApplicationContext.xml"})
public class BusinessServiceTest {

	private static HsqlDatabase database;

	@Autowired
	private BusinessService service;

	@BeforeClass
	public static void onlyOnce() {
		database = new HsqlDatabase();
		database.setUp("test");
	}

	@Test
	public void foo() {
		StringBuilder sb = new StringBuilder();
		sb.append("ID=1001, FirstName=John_Test, LastName=Doe_Test\n");
		sb.append("ID=1002, FirstName=James_Test, LastName=Brown_Test\n");
		sb.append("ID=1003, FirstName=Andrew_Test, LastName=Green_Test\n");

		String expectedValue = sb.toString();
		String actualValue = service.getValue();

		Assert.assertEquals("Service returns data that isn't deployed from \"test\" context", expectedValue,
				actualValue);
	}
}

If we run unit test we’ll get:

You can download source code here.

 

About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: