Writing Integration Tests for Spring Powered Repositories

After we have finished this lesson, we:

  • Understand what kind of a DbUnit data set we should use when we initialize our database into a known state.
  • Can write integration tests for Spring powered repositories.

Introduction to System Under Test

During this lesson we will write integration tests for a custom repository method that finds tasks from the database. This method is called: search(), and it takes the used search term as a method parameter. The implementation of the search() method fulfills these requirements:

  • It must return the information of the tasks whose title or description contains the given search term.
  • It must sort the returned tasks in ascending order by using the title of the returned task.
  • The search must be case-insensitive.

The source code of the interface that declares our custom repository methods looks as follows:

1import java.util.List;
2
3interface CustomTaskRepository {
4
5    List<TaskListDTO> findAll();
6
7    List<TaskListDTO> search(String searchTerm);
8}

Actually, we will write integration tests for the TaskRepository object. However, because we implemented our repository layer with Spring Data JPA, and we wanted to provide a custom implementation for these two finder methods, we had to declare them in a separate repository interface.

If you don’t know how you can add custom methods into a single Spring Data JPA repository, you should take a look at this blog post .

The TaskListDTO class contains the information of a single task that is returned by the search() method. Its source code looks as follows:

1public class TaskListDTO {
2
3    private Long id;
4    private String title;
5    private TaskStatus status;
6
7	//Getters and setters are omitted
8}

Next, we have to initialize our database into a known state before our integration tests are run. Let’s find out how we can do it.

Initializing Our Database Into a Known State

As we remember , we should use DbUnit data sets that are as small as possible. Also, because we are writing integration tests for a method that finds information from the database, we have to ensure that our data set allows us to test all required use cases.

This means that we have to initialize our database into a known state by creating a DbUnit data set that inserts two rows into the tasks table. We have to insert two rows into the tasks table because of these two reasons:

  • If we want to ensure that the tasks are returned in the correct order, we have to insert at least two rows into the tasks table.
  • We don’t have to insert any rows to the other database tables because the implementation of the search() method uses only the information that is found from the tasks table.

The name of our data set file is tasks.xml, and its source code looks as follows:

 1<dataset>
 2    <tasks id="1"
 3           assignee_id="1"
 4           closer_id="1"
 5           creation_time="2016-12-3 11:41:28"
 6           creator_id="1"
 7           description="This example contains integration tests"
 8           modification_time="2016-12-3 11:41:28"
 9           resolution="DONE"
10           status="CLOSED"
11           title="Write example application"
12           version="0"/>
13    <tasks id="2"
14           assignee_id="[null]"
15           closer_id="[null]"
16           creation_time="2016-12-4 11:41:28"
17           creator_id="1"
18           description="This lesson talks about integration testing"
19           modification_time="2016-12-4 11:41:28"
20           resolution="[null]"
21           status="OPEN"
22           title="Write lesson"
23           version="0"/>
24</dataset>

After we have created our DbUnit data set, we have to add the required constants into our constants class. We can declare the required constants by following these steps:

  1. Declare the constants which contain the search terms that we use when we write integration tests for our repository.
  2. Declare the constants which contain the information of the tasks found from our database. Because the search() method returns only the id, status, and title of the found tasks, we can ignore the other properties (for now).

After we have declared the required constants, the source code of the Tasks class looks as follows:

 1public final class Tasks {
 2
 3    private Tasks() {}
 4
 5    public static final String SEARCH_TERM_ONE_MATCH = "Esso";
 6    public static final String SEARCH_TERM_NOT_FOUND = "NO_PE";
 7
 8	public static class WriteExampleApp {
 9
10        public static final Long ID = 1L;
11        public static final TaskStatus STATUS = TaskStatus.CLOSED;
12        public static final String TITLE = "Write example application";
13    }
14
15    public static class WriteLesson {
16
17        public static final Long ID = 2L;
18        public static final TaskStatus STATUS = TaskStatus.OPEN;
19        public static final String TITLE = "Write lesson";
20    }
21}

The “real” Tasks class declares other constants as well. However, because these constants are not relevant for this lesson, we will ignore these constants (for now).

Also, we have to add these constants into the Tasks class because we will use these constants when we write integration tests for our controllers.

Next, we have to configure our integration tests.

Configuring Our Integration Test Class

Before we can write integration tests for our custom repository method, we have to add test specific configuration into our integration test class. We can add the required configuration into our test class by following these steps:

  1. Configure the DbUnit data set that is used to insert test data into our database. Because the tested method reads information from the database, our test methods can use the same data set. This means that we have to annotate our test class with the @DatabaseSetup annotation.
  2. Inject the tested TaskRepository object into our test class.

After we have configured our test class, its source code looks follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.DbUnitConfiguration;
 4import org.junit.experimental.categories.Category;
 5import org.junit.runner.RunWith;
 6import org.springframework.beans.factory.annotation.Autowired;
 7import org.springframework.test.context.ActiveProfiles;
 8import org.springframework.test.context.ContextConfiguration;
 9import org.springframework.test.context.TestExecutionListeners;
10import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
11import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
12import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
13import org.springframework.test.context.web.ServletTestExecutionListener;
14import org.springframework.test.context.web.WebAppConfiguration;
15
16import static org.assertj.core.api.Assertions.assertThat;
17
18@RunWith(SpringJUnit4ClassRunner.class)
19@ContextConfiguration(classes = {IntegrationTestContext.class})
20@WebAppConfiguration
21@TestExecutionListeners({
22        DependencyInjectionTestExecutionListener.class,
23        TransactionalTestExecutionListener.class,
24        DbUnitTestExecutionListener.class,
25        ServletTestExecutionListener.class
26})
27@DatabaseSetup("/com/testwithspring/intermediate/tasks.xml")
28@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
29@Category(IntegrationTest.class)
30@ActiveProfiles(Profiles.INTEGRATION_TEST)
31public class SearchTasksWhenNoTasksIsFoundTest {
32
33    @Autowired
34    private TaskRepository repository;
35}
I didn’t describe the full configuration of our integration test class because I have already covered it in this topic .

After we have configured our integration test cases, we can finally write integration tests for our repository.

Writing Integration Tests for Our Repository

Now we will write some integration tests for the search() method of the TaskRepository interface. Let’s start by verifying that the tested method is working as expected when no tasks is found.

Scenario 1: No Tasks Is Found

If we want to ensure that the tested method is working as expected when no tasks is found, we have to write an integration test which verifies that the search() method returns an empty list when no tasks is found.

After we have written this integration test, the source code of our test class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.DbUnitConfiguration;
 4import org.junit.Test;
 5import org.junit.experimental.categories.Category;
 6import org.junit.runner.RunWith;
 7import org.springframework.beans.factory.annotation.Autowired;
 8import org.springframework.test.context.ActiveProfiles;
 9import org.springframework.test.context.ContextConfiguration;
10import org.springframework.test.context.TestExecutionListeners;
11import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
13import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
14import org.springframework.test.context.web.ServletTestExecutionListener;
15import org.springframework.test.context.web.WebAppConfiguration;
16
17import java.util.List;
18
19import static org.assertj.core.api.Assertions.assertThat;
20
21@RunWith(SpringJUnit4ClassRunner.class)
22@ContextConfiguration(classes = {IntegrationTestContext.class})
23@WebAppConfiguration
24@TestExecutionListeners({
25        DependencyInjectionTestExecutionListener.class,
26        TransactionalTestExecutionListener.class,
27        DbUnitTestExecutionListener.class,
28        ServletTestExecutionListener.class
29})
30@DatabaseSetup("/com/testwithspring/intermediate/tasks.xml")
31@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
32@Category(IntegrationTest.class)
33@ActiveProfiles(Profiles.INTEGRATION_TEST)
34public class SearchTasksWhenNoTasksIsFoundTest {
35
36    @Autowired
37    private TaskRepository repository;
38
39    @Test
40    public void shouldReturnEmptyList() {
41        List<TaskListDTO> tasks = repository.search(Tasks.SEARCH_TERM_NOT_FOUND);
42        assertThat(tasks).isEmpty();
43    }
44}

Next, we will verify that the search() method is working as expected when one task is found.

Scenario 2: One Task Is Found

If we want to verify that the tested method is working as expected when one task is found, we have to write two integration tests:

  1. We have to verify that the search() method returns one task.
  2. We have to ensure that the search() method returns the correct task.

After we have written these integration tests, the source code of our integration test class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.DbUnitConfiguration;
 4import org.junit.Test;
 5import org.junit.experimental.categories.Category;
 6import org.junit.runner.RunWith;
 7import org.springframework.beans.factory.annotation.Autowired;
 8import org.springframework.test.context.ActiveProfiles;
 9import org.springframework.test.context.ContextConfiguration;
10import org.springframework.test.context.TestExecutionListeners;
11import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
13import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
14import org.springframework.test.context.web.ServletTestExecutionListener;
15import org.springframework.test.context.web.WebAppConfiguration;
16
17import java.util.List;
18
19import static org.assertj.core.api.Assertions.assertThat;
20
21@RunWith(SpringJUnit4ClassRunner.class)
22@ContextConfiguration(classes = {IntegrationTestContext.class})
23@WebAppConfiguration
24@TestExecutionListeners({
25        DependencyInjectionTestExecutionListener.class,
26        TransactionalTestExecutionListener.class,
27        DbUnitTestExecutionListener.class,
28        ServletTestExecutionListener.class
29})
30@DatabaseSetup("/com/testwithspring/intermediate/tasks.xml")
31@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
32@Category(IntegrationTest.class)
33@ActiveProfiles(Profiles.INTEGRATION_TEST)
34public class SearchTasksWhenOneTaskIsFoundTest {
35
36    @Autowired
37    private TaskRepository repository;
38
39    @Test
40    public void shouldReturnOneTask() {
41        List<TaskListDTO> tasks = repository.search(Tasks.SEARCH_TERM_ONE_MATCH);
42        assertThat(tasks).hasSize(1);
43    }
44
45    @Test
46    public void shouldReturnInformationOfCorrectTask() {
47        TaskListDTO found = repository.search(Tasks.SEARCH_TERM_ONE_MATCH)
48				.get(0);
49
50        assertThat(found.getId()).isEqualByComparingTo(Tasks.WriteLesson.ID);
51        assertThat(found.getStatus()).isEqualTo(Tasks.WriteLesson.STATUS);
52        assertThat(found.getTitle()).isEqualTo(Tasks.WriteLesson.TITLE);
53    }
54}

We have now written integration tests which cover two different scenarios. However, it should be clear that these tests are not enough because they don’t cover all boundary conditions, and these integration tests don’t help us to verify that the found tasks are returned in correct order.

That being said, these examples should help you to understand how you can write the remaining integration tests.

Let’s summarize what we learned from this lesson.

Summary

This lesson has taught us three things:

  • If we are writing integration tests for a method that finds information from the database, we should use a small data set that allows us to test all required use cases.
  • If the tested method reads information from the database, typically all test methods can use the same DbUnit data set.
  • We can get the tested repository object by injecting it into our test class.

Get the source code from Github