Unit testing JPA - Hibernate components with in-memory H2 database

author's avatar
Kees de Kooter
Jun 4 2020 9:50 • 2 min read

The goal

Be able to unit test database related components without the need to set up and entire RDBMS ecosystem. Enabling integration tests to be run by a continuous integration server.

The solution

Create a database on the fly, in-memory. This is achieved by using Maven, Springframework and Hibernate together.

Dependencies

I have tested with Derby (former Cloudscape). Derby provides an EmbeddedDriver. However files are still created in the filesystem.

HSQLDB should be able to do the trick but I was not able to get the configuration right due to a lack of proper documentation.

H2 delivered the final solution. I added the following dependency to my pom.xml:

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.3.160</version>
    <scope>test</scope>
</dependency>

Test class

Personally I am a big fan of TestNG. Mainly for the ability to group tests. Spring 2.5.x now has excellent annotation based support for TestNG.

If you your boss or your customer requires you to use JUnit then you can subclass AbstractTransactionalJUnit4SpringContextTests instead.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.testng.annotations.Test;

@Test(groups = { "integration" })
@ContextConfiguration(locations = { "/application-context.xml", "/test-application-context.xml" })
@TransactionConfiguration(defaultRollback=true)
public class TestSomeService extends AbstractTransactionalTestNGSpringContextTests {
   // bunch of test methods
}

After the main application-context.xml is loaded I load test-application-context.xml which overrides a couple of bean definitions.

Test application context

<?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"
    xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="org.h2.Driver"/>
        <property name="url" value="jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"/>
    </bean>

    <bean id="jpaAdaptor" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        <property name="showSql" value="false" />
        <!-- Let Hibernate generate the DDL for the schema -->
        <property name="generateDdl" value="true" />
        <property name="databasePlatform" value="org.hibernate.dialect.H2Dialect" />
    </bean>

</beans>

Initialize database schema

Hibernate has the ability to create a database schema on the fly the first time the SessionFactory is loaded. This is configured in two places. First the HibernateJpaVendorAdapter needs to be told to generateDdl. Second in the file META_INF/persistence.xml (the default JPA persistence context definition) I set the hibernate.hbm2ddl.auto to create.

    <persistence-unit name="some-unit" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>

        <properties>
            <!-- Auto detect annotated model classes -->
            <property name="hibernate.archive.autodetection" value="class" />

            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="create"/>
        </properties>
    </persistence-unit>

To fill the database with initial data add a file called import.sql containing insert statements to the classpath. To get a grasp of what hbm2ddl is doing we need to add the following logging configuration:

<logger name="org.hibernate.tool.hbm2ddl" additivity="false">
    <level value="debug" />
    <appender-ref ref="out" />
</logger>

References