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:

  1. 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.
  2. 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.
  3. 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}

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:

  1. Configure the used dataset file by annotating the integration test class with the @DatabaseSetup annotation.
  2. 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.
  3. 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:

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).

If you want to learn how to use Spring Data JPA, you should read my Spring Data JPA tutorial.