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());
    }
}

No comments:

Post a Comment