Writing Integration Tests for Spring Powered Repositories

After we have finished this lesson, we:

  • Can write integration tests for Spring powered repositories by using Spock Framework.
  • Understand how we should structure our specification classes when we are writing integration tests for Spring powered repositories.

Introduction to the System Under Specification

During this lesson we will write integration tests for a custom repository method that returns all tasks found from the database.

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 to 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 findAll() 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    public TaskListDTO() {}
 8
 9    //Getters and setters are omitted
10}

Next, we will find out how we can specify the expected behavior of our repository.

Specifying the Expected Behavior

We can specify the behavior of our repository by following these steps:

First, we have to create a new specification class and configure the system under specification. After we have configured the system under specification, the source code of our specification class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener
 2import com.github.springtestdbunit.annotation.DbUnitConfiguration
 3import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
 4import org.junit.experimental.categories.Category
 5import org.springframework.test.context.ActiveProfiles
 6import org.springframework.test.context.ContextConfiguration
 7import org.springframework.test.context.TestExecutionListeners
 8import org.springframework.test.context.web.WebAppConfiguration
 9import spock.lang.Specification
10
11@ContextConfiguration(classes = [IntegrationTestContext.class])
12@WebAppConfiguration
13@TestExecutionListeners(value = DbUnitTestExecutionListener.class,
14        mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
15)
16@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
17@Category(IntegrationTest.class)
18@ActiveProfiles(Profiles.INTEGRATION_TEST)
19class FindAllTasksSpec extends Specification {
20
21}
If you don’t know how you can configure the system under specification, you should take a look at the lesson: Configuring the System Under Specification .

Second, we have to inject the tested TaskRepository object into our specification class. After we have done this, the source code of our specification class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener
 2import com.github.springtestdbunit.annotation.DbUnitConfiguration
 3import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
 4import org.junit.experimental.categories.Category
 5import org.springframework.beans.factory.annotation.Autowired
 6import org.springframework.test.context.ActiveProfiles
 7import org.springframework.test.context.ContextConfiguration
 8import org.springframework.test.context.TestExecutionListeners
 9import org.springframework.test.context.web.WebAppConfiguration
10import spock.lang.Specification
11
12@ContextConfiguration(classes = [IntegrationTestContext.class])
13@WebAppConfiguration
14@TestExecutionListeners(value = DbUnitTestExecutionListener.class,
15        mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
16)
17@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
18@Category(IntegrationTest.class)
19@ActiveProfiles(Profiles.INTEGRATION_TEST)
20class FindAllTasksSpec extends Specification {
21
22    @Autowired
23    TaskRepository repository
24}

Third, we have to write a feature method that specifies the behavior of the findAll() method when no tasks is found from the database. We can write this feature method by following these steps:

  1. Add a new feature method to our specification class.
  2. Ensure that no tasks is found from the tasks database table.
  3. Define a variable that contains the tasks returned by the system under specification.
  4. Get all tasks from the database and store the returned tasks in the tasks variable.
  5. Ensure that the system under specification returns an empty list.

After we have created a new feature method, the source code of our specification class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener
 2import com.github.springtestdbunit.annotation.DatabaseSetup
 3import com.github.springtestdbunit.annotation.DbUnitConfiguration
 4import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
 5import org.junit.experimental.categories.Category
 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.web.WebAppConfiguration
11import spock.lang.Specification
12
13@ContextConfiguration(classes = [IntegrationTestContext.class])
14@WebAppConfiguration
15@TestExecutionListeners(value = DbUnitTestExecutionListener.class,
16        mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
17)
18@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
19@Category(IntegrationTest.class)
20@ActiveProfiles(Profiles.INTEGRATION_TEST)
21class FindAllTasksSpec extends Specification {
22
23    @Autowired
24    TaskRepository repository
25
26    @DatabaseSetup([
27        '/com/testwithspring/master/no-tasks-and-tags.xml'
28    ])
29    def 'Find all tasks when the database has no tasks'() {
30
31        def tasks
32
33        when: 'The tasks are queried from the database'
34        tasks = repository.findAll()
35
36        then: 'Should return an empty list'
37        tasks.isEmpty()
38    }
39}

The DbUnit data set that initializes our database into a known state (no-tasks-and-tags.xml) looks as follows:

1<dataset>
2    <tasks/>
3    <tags/>
4</dataset>

Fourth, we have to write a feature method that specifies the behavior of the findAll() method when two tasks are found from the database. We can write this feature method by following these steps:

  1. Add a new feature method to our specification class.
  2. Ensure that two tasks are found from the tasks database table and insert the required users into the users table.
  3. Define a variable that contains the tasks returned by the system under specification.
  4. Get all tasks from the database and store the returned tasks in the tasks variable.
  5. Ensure that the system under specification returns two tasks.
  6. Verify that the system under specification returns the information of the first task.
  7. Ensure that the system under specification returns the information of the second task.

After we have implemented our feature method, the source code of our specification class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener
 2import com.github.springtestdbunit.annotation.DatabaseSetup
 3import com.github.springtestdbunit.annotation.DbUnitConfiguration
 4import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
 5import org.junit.experimental.categories.Category
 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.web.WebAppConfiguration
11import spock.lang.Specification
12
13import static org.hamcrest.Matchers.hasSize
14
15@ContextConfiguration(classes = [IntegrationTestContext.class])
16@WebAppConfiguration
17@TestExecutionListeners(value = DbUnitTestExecutionListener.class,
18        mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
19)
20@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
21@Category(IntegrationTest.class)
22@ActiveProfiles(Profiles.INTEGRATION_TEST)
23class FindAllTasksSpec extends Specification {
24
25    @Autowired
26    TaskRepository repository
27
28    @DatabaseSetup([
29        '/com/testwithspring/master/no-tasks-and-tags.xml'
30    ])
31    def 'Find all tasks when the database has no tasks'() {
32
33        def tasks
34
35        when: 'The tasks are queried from the database'
36        tasks = repository.findAll()
37
38        then: 'Should return an empty list'
39        tasks.isEmpty()
40    }
41
42    @DatabaseSetup([
43            '/com/testwithspring/master/users.xml',
44            '/com/testwithspring/master/tasks.xml'
45    ])
46    def 'Find all tasks when the database has two tasks'() {
47
48        def tasks
49
50        when: 'The tasks are queried from the database'
51        tasks = repository.findAll()
52
53        then: 'Should return two tasks'
54        tasks hasSize(2)
55
56        and: 'Should return the information of the first task'
57        def first = tasks[0]
58
59        first.id == Tasks.WriteExampleApp.ID
60        first.status == Tasks.WriteExampleApp.STATUS
61        first.title == Tasks.WriteExampleApp.TITLE
62
63        and: 'Should return the information of the second task'
64        def second = tasks[1]
65
66        second.id == Tasks.WriteLesson.ID
67        second.status == Tasks.WriteLesson.STATUS
68        second.title == Tasks.WriteLesson.TITLE
69    }
70}

The DbUnit data set that inserts the required users into the users table (users.xml) looks as follows:

 1<dataset>
 2    <user_accounts id="1"
 3                   creation_time="2017-02-11 11:41:28"
 4                   email_address="john.doe@gmail.com"
 5                   is_enabled="true"
 6                   modification_time="2017-02-11 11:41:28"
 7                   name="John Doe"
 8                   password="user"
 9                   role="ROLE_USER"
10                   version="0"/>
11    <user_accounts id="2"
12                   creation_time="2017-02-11 11:41:28"
13                   email_address="anne.admin@gmail.com"
14                   is_enabled="true"
15                   modification_time="2017-02-11 11:41:28"
16                   name="Anne Admin"
17                   password="admin"
18                   role="ROLE_ADMIN"
19                   version="0"/>
20</dataset>

The DbUnit data set that inserts two tasks into the tasks table (tasks.xml) 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           modifier_id="1"
10           resolution="DONE"
11           status="CLOSED"
12           title="Write example application"
13           version="0"/>
14    <tasks id="2"
15           assignee_id="[null]"
16           closer_id="[null]"
17           creation_time="2016-12-4 11:41:28"
18           creator_id="1"
19           description="This lesson talks about integration testing"
20           modification_time="2016-12-4 11:41:28"
21           modifier_id="1"
22           resolution="[null]"
23           status="OPEN"
24           title="Write lesson"
25           version="0"/>
26
27    <tags id="1"
28          creation_time="2016-12-3 11:41:28"
29          modification_time="2016-12-3 11:41:28"
30          name="example"
31          version="0"/>
32    <tags id="2"
33          creation_time="2016-12-3 11:41:28"
34          modification_time="2016-12-3 11:41:28"
35          name="lesson"
36          version="0"/>
37
38    <tasks_tags task_id="1" tag_id="1"/>
39    <tasks_tags task_id="2" tag_id="2"/>
40</dataset>
The Tasks class is a constant class that contains the information of the tasks found from our database. If you want to get more information about constant classes, you should take a look at the lesson: DbUnit Best Practices: Using Test Data in Our Java Code .

When we specify the behavior of our repositories, we should follow these rules:

  • If the system under specification returns all rows of a database table, we should write multiple feature methods because otherwise we cannot ensure that the system under specification is working as expected when the database table is empty and when it contains data.
  • If the system under specification returns all rows that fulfill a search criteria, we should write only one feature method. We don’t need multiple feature methods because we can construct our test data and select the used search criteria in a way that allows us to test all required scenarios by using multiple when and then blocks.

We can now write integration tests for Spring powered repositories. Let’s summarize what we learned from this lesson.

Summary

This lesson has taught us three things:

  • We can inject the tested repository object into our specification class.
  • If the system under specification returns all rows of a database table, we should write multiple feature methods.
  • If the system under specification returns all rows that fulfill a search criteria, we should write only one feature method.

Get the source code from Github