My Spring Data JPA tutorial has taught us that we can create database queries and persist entities in the database by using special repository interfaces.
This raises an interesting question:
How can we write integration tests for our Spring Data JPA repositories because they are just interfaces?
This blog post answers to that question. During this blog post we will write integration tests for a Spring Data JPA repository that manages the information of todo entries (Todo objects). To be more specific, we will write integration tests for the findBySearchTerm() method of TodoRepository interface. That method ignores case and returns todo entries whose title or description contains the given search term.
Let’s start by getting the required dependencies with Maven.
There are two things that you should know before you read this blog post:
- If you are not familiar with Spring Data JPA, you should take a look at my Spring Data JPA Tutorial before you continue reading this blog post.
- If you want to write integration tests for Spring powered repositories that do not use Spring Data JPA, you can still use the approach described in this blog post.
Getting the Required Dependencies With Maven
We can get the required dependencies with Maven by declaring the following dependencies in our pom.xml file:
- JUnit (version 4.11).
- AssertJ Core (version 3.2.0). We use AssertJ for ensuring that the tested method returns the correct information.
- Spring Test (version 4.1.6.RELEASE).
- DbUnit (version 2.5.1). Remember to exclude the JUnit dependency. We use DbUnit for initializing our database into a known state before each test case is invoked.
- Spring Test DbUnit (version 1.2.1) integrates DbUnit with the Spring Test framework.
The relevant part of our pom.xml file looks as follows:
1<dependency>
2 <groupId>junit</groupId>
3 <artifactId>junit</artifactId>
4 <version>4.11</version>
5 <scope>test</scope>
6</dependency>
7<dependency>
8 <groupId>org.assertj</groupId>
9 <artifactId>assertj-core</artifactId>
10 <version>3.2.0</version>
11 <scope>test</scope>
12</dependency>
13<dependency>
14 <groupId>org.springframework</groupId>
15 <artifactId>spring-test</artifactId>
16 <version>4.1.6.RELEASE</version>
17 <scope>test</scope>
18</dependency>
19<dependency>
20 <groupId>org.dbunit</groupId>
21 <artifactId>dbunit</artifactId>
22 <version>2.5.1</version>
23 <scope>test</scope>
24 <exclusions>
25 <exclusion>
26 <artifactId>junit</artifactId>
27 <groupId>junit</groupId>
28 </exclusion>
29 </exclusions>
30</dependency>
31<dependency>
32 <groupId>com.github.springtestdbunit</groupId>
33 <artifactId>spring-test-dbunit</artifactId>
34 <version>1.2.1</version>
35 <scope>test</scope>
36</dependency>
After we have configured the required dependencies in our pom.xml file, we can configure our integration tests.
Configuring Our Integration Tests
We can configure our integration tests by following these steps:
- Run integration tests by using the SpringJUnit4ClassRunner class. It is a custom JUnit runner that integrates the Spring Test framework with JUnit. We can configure the used JUnit runner by annotating our test class with the @RunWith annotation.
- Configure the application context configuration class (or XML configuration file) that configures the application context used by our integration tests. We can configure the used application context configuration class (or XML configuration file) by annotating our test class with the @ContextConfiguration annotation.
- Configure the test execution listeners which react to the test execution events that are published by the Spring Test framework. We have to configure the following test execution listeners:
- The DependencyInjectionTestExecutionListener provides dependency injection for the test object.
- The TransactionalTestExecutionListener adds transaction support (with default rollback semantics) into our integration tests.
- The DbUnitTestExecutionListener adds support for the features provided by the Spring Test DbUnit library.
After we have added this configuration into our integration test class, its source code looks as follows:
1import com.github.springtestdbunit.DbUnitTestExecutionListener;
2import org.junit.runner.RunWith;
3import org.springframework.test.context.ContextConfiguration;
4import org.springframework.test.context.TestExecutionListeners;
5import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
6import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
7import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
8
9@RunWith(SpringJUnit4ClassRunner.class)
10@ContextConfiguration(classes = {PersistenceContext.class})
11@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
12 TransactionalTestExecutionListener.class,
13 DbUnitTestExecutionListener.class})
14public class ITFindBySearchTermTest {
15}
Additional Reading:
- Spring Framework Reference Documentation: 11.3 Integration Testing
- Spring Framework Reference Documentation: 14.5.2 TestExecutionListener configuration
- The Javadoc of the @RunWith annotation
- The Javadoc of the SpringJUnit4ClassRunner class
- The Javadoc of the @ContextConfiguration annotation
- The Javadoc of the @TestExecutionListeners annotation
- The Javadoc of the TestExecutionListener interface
- The Javadoc of the DependencyInjectionTestExecutionListener class
- The Javadoc of the TransactionalTestExecutionListener class
After we have configured our integration test class, we can start writing integration tests for our Spring Data JPA repository.
Writing Integration Tests for Our Repository
We can write integration tests for our repository by following these steps:
First, we have to inject the tested repository into our test class. Because we are writing integration tests for the TodoRepository interface, we have to inject it into our test class. The source code of our test class looks as follows:
1import com.github.springtestdbunit.DbUnitTestExecutionListener;
2import org.junit.runner.RunWith;
3import org.springframework.beans.factory.annotation.Autowired;
4import org.springframework.test.context.ContextConfiguration;
5import org.springframework.test.context.TestExecutionListeners;
6import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
7import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
8import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
9
10@RunWith(SpringJUnit4ClassRunner.class)
11@ContextConfiguration(classes = {PersistenceContext.class})
12@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
13 TransactionalTestExecutionListener.class,
14 DbUnitTestExecutionListener.class})
15public class ITFindBySearchTermTest {
16
17 @Autowired
18 private TodoRepository repository;
19}
Second, we have to create the DbUnit dataset that initializes our database into a known state before our test cases are invoked. We will use the flat XML dataset format because it is less verbose than the original DbUnit dataset format. This means that we can create our dataset by following these rules:
- Each XML element contains the information of a single table row.
- The name of the XML element identifies the name of the database table in which its information is inserted.
- The attributes of the XML element specify the values that are inserted into the columns of the database table.
The tested repository (TodoRepository) queries information from the todos table that has the following columns: id, created_by_user, creation_time, description, modified_by_user, modification_time, title, and version.
Because we are writing integration tests for a method that returns a list of Todo objects, we want to insert two rows to the todos table. We can do this by creating a DbUnit dataset file (todo-entries.xml) that looks as follows:
1<dataset>
2 <todos id="1"
3 created_by_user="createdByUser"
4 creation_time="2014-12-24 11:13:28"
5 description="description"
6 modified_by_user="modifiedByUser"
7 modification_time="2014-12-25 11:13:28"
8 title="title"
9 version="0"/>
10 <todos id="2"
11 created_by_user="createdByUser"
12 creation_time="2014-12-24 11:13:28"
13 description="tiscription"
14 modified_by_user="modifiedByUser"
15 modification_time="2014-12-25 11:13:28"
16 title="Foo bar"
17 version="0"/>
18</dataset>
Additional reading:
DbUnit documentation provides more information about the different DbUnit dataset formats.
Third, we can write integration tests for the findBySearchTerm() method of the TodoRepository interface. Let’s write integration tests which ensure that the findBySearchTerm() method is working correctly when the title of one todo entry contains the given search term. We can write these integration tests by following these steps:
- Configure the used dataset file by annotating the integration test class with the @DatabaseSetup annotation.
- Write an integration test which ensures that the findBySearchTerm() method returns one todo entry when the search term “iTl” is passed as a method parameter.
- Write an integration test which ensures that the findBySearchTerm() method returns the “first” todo entry when the search term “iTl” is passed as a method parameter.
The source code of the ITFindBySearchTerm class looks as follows:
1import com.github.springtestdbunit.DbUnitTestExecutionListener;
2import com.github.springtestdbunit.annotation.DatabaseSetup;
3import org.junit.Test;
4import org.junit.runner.RunWith;
5import org.springframework.beans.factory.annotation.Autowired;
6import org.springframework.test.context.ContextConfiguration;
7import org.springframework.test.context.TestExecutionListeners;
8import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
9import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
10import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
11
12import static org.assertj.core.api.Assertions.assertThat;
13
14@RunWith(SpringJUnit4ClassRunner.class)
15@ContextConfiguration(classes = {PersistenceContext.class})
16@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
17 TransactionalTestExecutionListener.class,
18 DbUnitTestExecutionListener.class})
19@DatabaseSetup("todo-entries.xml")
20public class ITFindBySearchTermTest {
21
22 @Autowired
23 private TodoRepository repository;
24
25 @Test
26 public void findBySearchTerm_TitleOfFirstTodoEntryContainsGivenSearchTerm_ShouldReturnOneTodoEntry() {
27 List<Todo> searchResults = repository.findBySearchTerm("iTl");
28 assertThat(searchResults).hasSize(1);
29 }
30
31 @Test
32 public void findBySearchTerm_TitleOfFirstTodoEntryContainsGivenSearchTerm_ShouldReturnFirstTodoEntry() {
33 List<Todo> searchResults = repository.findBySearchTerm("iTl");
34
35 Todo found = searchResults.get(0);
36 assertThat(found.getId()).isEqualTo(1L);
37 }
38}
When you use the @DatabaseSetup annotation, you have to follow these rules:
- If all test methods of your test class use the same dataset, you can configure it by annotating your test class with @DatabaseSetup annotation. However, if all test methods of your test class do not use the same dataset, you have to annotate your test methods with the @DatabaseSetup annotation.
- If the dataset file is in the same package than the integration test class, you can configure it by using the name of the dataset file. On the other hand, if the dataset file is not in same the package than the test class, you have to configure the full path of the dataset file. For example, if your dataset file (todo-entries.xml) is in the package foo.bar, you can configure its full path by using the string: “/foo/bar/todo-entries.xml”.
Additional Reading:
- Writing Tests for Data Access Code is a five-part tutorial that describes how you can write tests for your data access code, and ensure that your tests are clean and easy to maintain.
- Spring From the Trenches: Using Null Values in DbUnit Datasets describes why you should use null values in your DbUnit datasets and explains how you can use them.
- Spring From the Trenches: Resetting Auto Increment Columns Before Each Test Method describes why you should reset the auto increment columns before each test method and explains how you can do it.
Let’s move on and summarize what we learned from this blog post.
Summary
This blog post has taught us four things:
- We can integrate DbUnit with the Spring Test framework by using Spring Test DbUnit.
- We can integrate Spring Test DbUnit with the Spring Test framework by using the DbUnitTestExecutionListener class.
- We should use the flat XML databaset format because it is less verbose than the original DbUnit dataset format.
- We can use the @DatabaseSetup annotation on the class level or on the method level.
P.S. You can get the example applications of this blog post from Github (query methods, JPA Criteria API, Querydsl).