Wednesday, 2 March 2016

JUnit + H2 + Spring: Unit testing with in-memory database

Ussually web applications developed with Spring use a database for storing and reading data. Creating unit tests that are A TRIP (See this unit testing cheat sheat) cand turn out to be kind of painfull.

Luckyly for us we can achive that easily with Junit, Spring, Maven and H2. We will use an in-memory database that Spring will create and populate for us before the tests. The database will always be in a known state before and while running the tests, so we don't get nasty fails because of inconsistent data.

pom configuration
 
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.187</version>
</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>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</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.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
.....
<plugins>
<plugin>
<version>2.19</version>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
</plugins>
view raw pom.xml hosted with ❤ by GitHub

test-config.xml
In your src/test/resources create a file named test-config.xml.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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-4.0.xsd">
<bean id="entityManagerFactory" autowire="byName"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!-- <property name="dataSource" ref="wiklimbDataSource" /> -->
<property name="persistenceUnitName" value="wiklimb" />
<property name="dataSource" ref="dataSource" />
<property name="persistenceXmlLocation" value="classpath:META-INF/h2-persistence.xml" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
</bean>
</property>
</bean>
<context:component-scan base-package="org.wiklimb.dao,org.wiklimb.service" />
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<!-- to get the entity manager -->
<tx:annotation-driven transaction-manager="transactionManager" />
<jdbc:embedded-database id="dataSource" type="H2">
<jdbc:script location="classpath:test.sql" />
<jdbc:script location="classpath:data.sql" />
</jdbc:embedded-database>
</beans>
view raw test-config.xml hosted with ❤ by GitHub
In this file we are creating a database in the jdbc:embedded-database tag. Spring will create an H2 database instance and execute the scripts test.sql and data.sql when setting up the context.  

h2-persistence.xml
You also need to create the file src/test/resources/META-INF/h2-persistence.xml. Notice that there are no connection configuration on the persistence file.
<?xml version="1.0"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="wiklimb" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>your classes here</class>
<properties>
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.archive.autodetection" value="" />
</properties>
</persistence-unit>
</persistence>


Database for testing
Create a test.sql and a data.sql files with the ddl and the data for your tests. Notice that h2 sql has it's own markup so you may need adjust the sql generated from your db.  

The tests
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:test-config.xml" })
public class PartyServiceTest {
@Autowired
private PartyService service;
@Autowired
private NotificationService notService;
@Test
@Transactional
public void acceptReq(){
service.acceptRequest(new Joinrequest(null, party, user), admin);
Assert.assertTrue(service.getPartyRequests(party).isEmpty());
Assert.assertEquals(2,service.getPartyUsers(party).size());
Assert.assertEquals(EventType.PARTY_REQUEST_ACCEPTED,notService.getLatestNotifications(1, 1).get(0).getType());
}
You just need to annotate your test with @RunWith(SpringJUnit4ClassRunner.class) and @ContextConfiguration(locations = { "classpath:test-config.xml" }). This will get running the spring context for the tests, and the h2 database with it.

Using @Transactional
If you annotate a test with @Transactional the entire unit test will be inside one transaction thath will be rolledback after the test, keeping the database data clean after the test.

Service and DAO tip
If your Service layer is annotated with @Transactional and your Dao layer isn't (daos shouldn't have  @Transactional)  test just one layer on each test case. For example, if you are testing the PartyService don't use the PartyDAO for the asserts, as the PartyDAO won't see changes created at the service functions.

You can view a full working example in wrex https://github.com/konum/wrex/