Tuesday, 19 May 2015

Database Testing with H2

H2 is an in-memory database which can be really useful for running unit tests against.  It allows the JPA annotated classes (Entities) to be used to write to and read from a real database.

Schema Generation

Use the previous post on Schema Generation from JPA objects to make sure that a schema.sql file exists in the test resources directory.  This can be used to create the database tables for H2 to then use.

Pom Dependencies

There are a few dependencies that need to be included in the pom file if they aren't already.  These include JUnit, H2 and spring, as well as hibernate entity manager.

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.3.174</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>${spring.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>${hibernate.version}</version>
        <scope>test</scope>
    </dependency>

Spring Context for H2

This spring context (domain-test-context.xml) defines the use of H2 as the data source and initialises the database with a createSchema.sql file and the schema.sql file generated above by the hibernate4 maven plugin.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="  
        http://www.springframework.org/schema/beans       
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd  
        http://www.springframework.org/schema/tx       
        http://www.springframework.org/schema/tx/spring-tx-4.0.xsd  
        http://www.springframework.org/schema/jdbc   
        http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd" >

    
    <jdbc:embedded-database id="dataSource" type="H2" />
    
    <jdbc:initialize-database data-source="dataSource" ignore-failures="ALL">
        <jdbc:script location="createSchema.sql" />
        <jdbc:script location="schema.sql" />
        <!-- We could put other initialising data stuff in here -->
    </jdbc:initialize-database>
    
    <!-- This manages the entities and interactions with the database -->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="packagesToScan" value="com.my.classes.impl" />
        <property name="dataSource" ref="wiDataSource" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="showSql" value="false" />
                <property name="databasePlatform" value="org.hibernate.dialect.H2Dialect" />
                <property name="generateDdl" value="false" />
            </bean>
        </property>
        <property name="jpaProperties">
            <props>
                <!-- General settings -->
                <prop key="hibernate.archive.autodetection">class, hbm</prop>
                <prop key="hibernate.current_session_context_class">jta</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.format_sql">true</prop>
            </props>
        </property>
    </bean>
    
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
    
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>
    
    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
       
    <bean id="myRepository" class="com.my.classes.repo.MyRepository" />
</beans>

This context creates the H2 database and wires the MyRepository ready for testing.

The createSchema.sql file contains the line as the schema.sql generated won't generate a database schema itself only the tables / columns which are part of that schema.

create schema MY_SCHEMA;

Test Class

This is the sample test class.  It uses the Spring Runner with the text context above.  It is ApplicationContextAware so that we can load the repository (@Autowire could be used instead) and trust spring to use the PersistenceAnnotationBeanPostProcessor to inject the EntityManager.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:domain-test-context.xml"})
public class WorkInstructionRepositoryTest implements ApplicationContextAware
{
    /**
     * This is the application context that we can load beans from.
     */
    private ApplicationContext applicationContext;

    /**
     * JPA Entity Manager.
     */
    @PersistenceContext
    private EntityManager entityManager;

    /**
     * The class under test.
     */
    private MyRepository repo;

    /**
     * {@inheritDoc}
     */
    @Override
    public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException
    {
        this.applicationContext = applicationContext;
    }

    /**
     * Before each test get the repository.
     */
    @Before
    public void before()
    {
        this.repo = (MyRepository) applicationContext.getBean("myRepository");
    }

    /**
     * Test creating and loading some data.
     */
    @Test
    @Transactional
    @Rollback(true)
    public void testCreateAndReadData()
    {
        // Arrange
        final SomeData data = new SomeData();
        data.setActive(true);
        data.setText("Some Data Text");

        // Act
        repo.createData(data);
        final List<DataInterface> loadedDataObjs = repo.getAllData();

        // Assert
        final DataInterface loadedData = loadedDataObjs.get(0);
        assertEquals(data.isActive(), loadedData.isActive());
        assertEquals(data.getText(), loadedData.getText());
    }
}

JPA Schema Generator

This is a sample of using the de.juplo plugin for generating a database schema from annotated JPA Entity classes.

The Config


 <plugin>
    <groupId>de.juplo</groupId>
    <artifactId>hibernate4-maven-plugin</artifactId>
    <version>1.1.0</version>
    <executions>
        <execution>
            <id>package</id>
            <phase>prepare-package</phase>
            <goals>
                <goal>export</goal>
            </goals>
            <configuration>
                 <hibernateDialect>org.hibernate.dialect.MySQL5Dialect</hibernateDialect>
            </configuration>
        </execution>
        <execution>
            <id>test</id>
            <phase>process-test-resources</phase>
            <goals>
                <goal>export</goal>
            </goals>
            <configuration>
                <outputFile>${project.build.testOutputDirectory}/schema.sql</outputFile>
                <hibernateDialect>org.hibernate.dialect.H2Dialect</hibernateDialect>
            </configuration>
        </execution>
    </executions>
    <configuration>
        <target>SCRIPT</target>
    </configuration>
 </plugin>

Note that the normal configuration doesn't need an outputFile because it is generated in the target directory by default.  This example uses two executions, the first for the standard target build and the second for a test build for use with H2 for unit testing.
The <target>SCRIPT</target> part means that a sql script is created rather than trying to execute this directly on a database.

For more information see http://juplo.de/hibernate4-maven-plugin/