Writing Integration Tests for a Spring Repository With Kotlin and JUnit 5: Reading Data
After we have finished this lesson, we can write integration tests for repository functions which read data from the database.
This lessons assumes that:
Get the source code from Github
Introduction to the System Under Test
During this lesson we will write integration tests for the findAll() function of the TaskRepository class. This function has no function parameters and it returns a list of TaskListItemDTO objects. To be more specific, this function returns a list of tasks found from the database and sorts the returned list in ascending order by using the id of the task as a sort criteria.
The source code of the TaskListItemDTO class looks as follows:
1data class TaskListItemDTO(
2 val id: Long,
3 val title: String,
4 val status: TaskStatus
5)
The source code of the TaskStatus enum looks as follows:
1enum class TaskStatus {
2 CLOSED,
3 IN_PROGRESS,
4 OPEN
5}
Next, we will find out how we can write integration tests for our repository function.
Writing Integration Tests for Our Repository Function
We can write integration tests for the findAll() function by following these steps:
First, we have to create a test class that contains our integration test functions and annotate the test class with @DatabaseIntegrationTest annotation. After we have created our test class, we have to create a private read-only property which contains a TaskRepository object and inject the value of this property by using constructor injection.
After we have done this, the source code of our test class looks as follows:
1import org.junit.jupiter.api.DisplayName
2import org.springframework.beans.factory.annotation.Autowired
3
4@DatabaseIntegrationTest
5@DisplayName("Find all tasks from the database")
6class FindAllTasksTest(@Autowired private val repository: TaskRepository) {
7
8}
Second, we have to ensure that the system under test is working as expected when no tasks are found from the database. We can write the required integration test by following these steps:
- Create an inner class which contains our integration tests.
- Annotate the inner class with the
@DatabaseIntegrationTestannotation. This is required because the Spring TestContext framework doesn’t have a proper support for nested test classes. - Insert the test data into the database. Our DbUnit data sets must ensure that our database has no tasks and tags.
- Write a test function which ensures that the
findAll()function returns an empty list.
After we have written the required integration tests, the source code of our test class looks as follows:
1import com.github.springtestdbunit.annotation.DatabaseSetup
2import org.assertj.core.api.Assertions.assertThat
3import org.junit.jupiter.api.DisplayName
4import org.junit.jupiter.api.Nested
5import org.junit.jupiter.api.Test
6import org.springframework.beans.factory.annotation.Autowired
7
8@DatabaseIntegrationTest
9@DisplayName("Find all tasks from the database")
10class FindAllTasksTest(@Autowired private val repository: TaskRepository) {
11
12 @Nested
13 @DisplayName("When no tasks are found")
14 @DatabaseIntegrationTest
15 @DatabaseSetup(value = [
16 "/com/testwithspring/master/users.xml",
17 "/com/testwithspring/master/no-tasks-and-tags.xml"
18 ])
19 inner class WhenNoTasksAreFound {
20
21 @Test
22 @DisplayName("Should return an empty list")
23 fun shouldReturnEmptyList() {
24 val tasks = repository.findAll()
25 assertThat(tasks).isEmpty()
26 }
27 }
28}
The DbUnit data set which inserts the user data into our database (users.xml) looks as follows:
1<dataset>
2 <user_accounts id="1"
3 email_address="john.doe@gmail.com"
4 is_enabled="true"
5 name="John Doe"
6 password="user"
7 role="ROLE_USER"/>
8 <user_accounts id="2"
9 email_address="anne.admin@gmail.com"
10 is_enabled="true"
11 name="Anne Admin"
12 password="admin"
13 role="ROLE_ADMIN"/>
14</dataset>
The DbUnit data set which ensures that our database has no tasks and tags (no-tasks-and-tags.xml) looks as follows:
1<dataset>
2 <tasks/>
3 <tags/>
4 <tasks_tags/>
5</dataset>
Third, we have to write the integration tests which ensure that the system under test is working as expected when two tasks are found from the database. We can write these tests by following these steps:
- Create an inner class which contains our integration tests.
- Annotate the inner class with the
@DatabaseIntegrationTestannotation. - Ensure that we can inject a
SoftAssertionsobject into our test function. We will use soft assertions when we write assertions for the returned objects. - Insert the test data into the database. Our DbUnit data sets must ensure that our database has two tasks.
- Ensure that our DbUnit data sets are loaded by the
ReplacementDataSetLoaderclass. This class ensures that we can insertnullcolumn values into our database. - Write a test function which ensures that the
findAll()function returns two tasks. - Write a test function which verifies that the
findAll()function returns the oldest task as the first task of the returned list. - Write a test function which ensures that the
findAll()function returns the newest task as the second task of the returned list.
After we have written our integration tests, the relevant part of our test class looks as follows:
1import com.github.springtestdbunit.annotation.DatabaseSetup
2import com.github.springtestdbunit.annotation.DbUnitConfiguration
3import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
4import org.assertj.core.api.Assertions.assertThat
5import org.assertj.core.api.SoftAssertions
6import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension
7import org.junit.jupiter.api.DisplayName
8import org.junit.jupiter.api.Nested
9import org.junit.jupiter.api.Test
10import org.junit.jupiter.api.extension.ExtendWith
11import org.springframework.beans.factory.annotation.Autowired
12
13@DatabaseIntegrationTest
14@DisplayName("Find all tasks from the database")
15class FindAllTasksTest(@Autowired private val repository: TaskRepository) {
16
17 @Nested
18 @DisplayName("When two tasks are found")
19 @DatabaseIntegrationTest
20 @ExtendWith(SoftAssertionsExtension::class)
21 @DatabaseSetup(value = [
22 "/com/testwithspring/master/users.xml",
23 "/com/testwithspring/master/tasks.xml"
24 ])
25 @DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
26 inner class WhenTwoTasksAreFound {
27
28 @Test
29 @DisplayName("Should return two tasks")
30 fun shouldReturnTwoTasks() {
31 val tasks = repository.findAll()
32 assertThat(tasks).hasSize(2)
33 }
34
35 @Test
36 @DisplayName("Should return the oldest task as the first task of the returned list")
37 fun shouldReturnOldestTaskAsFirstTaskOfReturnedList(
38 assertions: SoftAssertions
39 ) {
40 val firstTask = repository.findAll()[0]
41
42 assertions.assertThat(firstTask.id)
43 .`as`("id")
44 .isEqualByComparingTo(Tasks.WriteExampleApplication.ID)
45 assertions.assertThat(firstTask.title)
46 .`as`("title")
47 .isEqualTo(Tasks.WriteExampleApplication.TITLE)
48 assertions.assertThat(firstTask.status)
49 .`as`("status")
50 .isEqualTo(Tasks.WriteExampleApplication.STATUS)
51 }
52
53 @Test
54 @DisplayName("Should return the newest task as the second task of the returned list")
55 fun shouldReturnNewestTaskAsSecondTaskOfReturnedList(
56 assertions: SoftAssertions
57 ) {
58 val secondTask = repository.findAll()[1]
59
60 assertions.assertThat(secondTask.id)
61 .`as`("id")
62 .isEqualByComparingTo(Tasks.WriteLesson.ID)
63 assertions.assertThat(secondTask.title)
64 .`as`("title")
65 .isEqualTo(Tasks.WriteLesson.TITLE)
66 assertions.assertThat(secondTask.status)
67 .`as`("status")
68 .isEqualTo(Tasks.WriteLesson.STATUS)
69 }
70 }
71}
Additional Reading:
The DbUnit data set which inserts two tasks into the database (tasks.xml) looks as follows:
1<dataset>
2 <tasks id="1"
3 assignee_id="1"
4 closer_id="1"
5 creator_id="1"
6 description="This example contains integration tests"
7 modifier_id="1"
8 resolution="DONE"
9 status="CLOSED"
10 title="Write example application"/>
11 <tasks id="2"
12 assignee_id="[null]"
13 closer_id="[null]"
14 creator_id="1"
15 description="This lesson talks about integration testing"
16 modifier_id="1"
17 resolution="[null]"
18 status="OPEN"
19 title="Write lesson"/>
20
21 <tags id="1"
22 name="example"/>
23 <tags id="2"
24 name="lesson"/>
25
26 <tasks_tags task_id="1" tag_id="1"/>
27 <tasks_tags task_id="2" tag_id="2"/>
28</dataset>
The Tasks class is a constant class which contains the column values used in our DbUnit data set. Its source code looks as follows:
1class Tasks {
2
3 class WriteExampleApplication {
4
5 companion object {
6
7 const val ID = 1L
8 const val TITLE = "Write example application"
9 val STATUS = TaskStatus.CLOSED
10 }
11 }
12
13 class WriteLesson {
14
15 companion object {
16
17 const val ID = 2L;
18 const val TITLE = "Write lesson"
19 val STATUS = TaskStatus.OPEN
20 }
21 }
22}
We can now write integration tests for repository functions which read data from the database. Let’s summarize what we learned from this lesson.
Summary
This lesson has taught us two things:
- Because the Spring TestContext framework doesn’t have a proper support for nested test classes, we have to annotate our inner test classes with the
@DatabaseIntegrationTestannotation. - If we want to write soft assertions by using
SoftAssertionsobjects which are injected into our test functions, we have to use theSoftAssertionsExtension.