Writing Integration Tests for a Spring Repository With Kotlin and JUnit 5: Writing Data

After we have finished this lesson, we can write integration tests for repository functions which write data to the database.

Get the source code from Github

Introduction to the System Under Test

During this lesson we will write integration tests for the create() function of the TaskRepository class. This function takes a CreateTask object, which contains the information of the created task, as a function parameter. After this function has inserted a new task into the database, this function returns a Task object which contains the information of the created task.

The source code of our domain model looks as follows:

 1data class Creator(val id: Long)
 2
 3enum class TaskStatus {
 4    CLOSED,
 5    IN_PROGRESS,
 6    OPEN
 7}
 8
 9data class CreateTask(
10        val creator: Creator,
11        val description: String?,
12        val status: TaskStatus,
13        val title: String
14)
15
16data class Assignee(val id: Long)
17
18data class Closer(val id: Long)
19
20data class Modifier(val id: Long)
21
22enum class TaskResolution {
23    DUPLICATE,
24    DONE,
25    WONT_DO
26}
27
28data class TaskTag(val id: Long, val name: String)
29
30data class Task(val assignee: Assignee?,
31                val closer: Closer?,
32                val creator: Creator,
33                val description: String?,
34                val id: Long,
35                val modifier: Modifier,
36                val resolution: TaskResolution?,
37                val status: TaskStatus,
38                val tags: List<TaskTag>,
39                val title: String)

Next, we will find out how we can configure our integration tests.

Configuring Our Integration Tests

We can configure our integration tests by following these steps:

First, we have to create a new integration test class and annotate the test class with the @DatabaseIntegrationTest annotation. After we have created this class, we have to add one constant to our test class.

The source code of our integration test class looks as follows:

 1import com.testwithspring.master.DatabaseIntegrationTest
 2import org.junit.jupiter.api.DisplayName
 3
 4@DatabaseIntegrationTest
 5@DisplayName("Create a new task")
 6class CreateNewTaskTest {
 7
 8    companion object {
 9
10        private val NEXT_FREE_ID = 1L
11    }
12}

Second, we have to ensure that we can inject SoftAssertions objects into our test functions. After we have done this, the source code of our test class looks as follows:

 1import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension
 2import org.junit.jupiter.api.DisplayName
 3import org.junit.jupiter.api.extension.ExtendWith
 4
 5@DatabaseIntegrationTest
 6@ExtendWith(SoftAssertionsExtension::class)
 7@DisplayName("Create a new task")
 8class CreateNewTaskTest {
 9
10    companion object {
11
12        private val NEXT_FREE_ID = 1L
13    }
14}

Third, we have to insert the test data into the database. Our DbUnit data sets must ensure that:

  • Our database has two users.
  • Our database has no tasks and tags.

After we have inserted our test data into our database, we must ensure that our DbUnit data sets are loaded by the ReplacementDataSetLoader class. After we have done this, the source code of our integration 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.junit.jupiter.SoftAssertionsExtension
 5import org.junit.jupiter.api.DisplayName
 6import org.junit.jupiter.api.extension.ExtendWith
 7
 8@DatabaseIntegrationTest
 9@ExtendWith(SoftAssertionsExtension::class)
10@DatabaseSetup(value = [
11    "/com/testwithspring/master/users.xml",
12    "/com/testwithspring/master/no-tasks-and-tags.xml"
13])
14@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
15@DisplayName("Create a new task")
16class CreateNewTaskTest {
17
18    companion object {
19
20        private val NEXT_FREE_ID = 1L
21    }
22}

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>

Fourth, we have to create two private read-only properties and inject the values of these properties by using constructor injection. These properties are:

  • The jdbcTemplate property contains a NamedParameterJdbcTemplate object which allows us to reset the auto-increment column of the tasks database table.
  • The repository property contains the tested TaskRepository object.

After we have added these properties to our test class, its source code 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.junit.jupiter.SoftAssertionsExtension
 5import org.junit.jupiter.api.DisplayName
 6import org.junit.jupiter.api.extension.ExtendWith
 7import org.springframework.beans.factory.annotation.Autowired
 8import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
 9
10@DatabaseIntegrationTest
11@ExtendWith(SoftAssertionsExtension::class)
12@DatabaseSetup(value = [
13    "/com/testwithspring/master/users.xml",
14    "/com/testwithspring/master/no-tasks-and-tags.xml"
15])
16@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
17@DisplayName("Create a new task")
18class CreateNewTaskTest(
19        @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
20        @Autowired private val repository: TaskRepository
21) {
22
23    companion object {
24
25        private val NEXT_FREE_ID = 1L
26    }
27}

Fifth, add a private read-only property to our test class. This property contains an IdColumnReset object that can reset the auto-increment columns of the specified database tables.

After we have added this property to our test class, its source code 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.junit.jupiter.SoftAssertionsExtension
 5import org.junit.jupiter.api.DisplayName
 6import org.junit.jupiter.api.extension.ExtendWith
 7import org.springframework.beans.factory.annotation.Autowired
 8import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
 9
10@DatabaseIntegrationTest
11@ExtendWith(SoftAssertionsExtension::class)
12@DatabaseSetup(value = [
13    "/com/testwithspring/master/users.xml",
14    "/com/testwithspring/master/no-tasks-and-tags.xml"
15])
16@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
17@DisplayName("Create a new task")
18class CreateNewTaskTest(
19        @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
20        @Autowired private val repository: TaskRepository
21) {
22
23    companion object {
24
25        private val NEXT_FREE_ID = 1L
26    }
27
28    private val idColumnReset = IdColumnReset(jdbcTemplate)
29}

The source code of the IdColumnReset class looks as follows:

 1import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
 2
 3class IdColumnReset internal constructor(
 4        private val jdbcTemplate: NamedParameterJdbcTemplate
 5) {
 6
 7    companion object {
 8        private const val QUERY_RESET_ID_COLUMN_TEMPLATE =
 9                "ALTER SEQUENCE %s RESTART WITH 1"
10    }
11
12    fun resetIdColumns(vararg tableNames: String) {
13        tableNames.forEach {
14            val query = String.format(
15                    QUERY_RESET_ID_COLUMN_TEMPLATE,
16                    it + "_id_seq"
17            )
18            jdbcTemplate.update(query, emptyMap<String, String>())
19        }
20    }
21}
If you want to get more information about this technique, you should take a look at the lesson: Resetting Generated Id Column Values Before Every Test Method .

Sixth, we have to create the input data that’s passed to the tested repository function. After we have done this, the source code 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.junit.jupiter.SoftAssertionsExtension
 5import org.junit.jupiter.api.DisplayName
 6import org.junit.jupiter.api.extension.ExtendWith
 7import org.springframework.beans.factory.annotation.Autowired
 8import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
 9
10@DatabaseIntegrationTest
11@ExtendWith(SoftAssertionsExtension::class)
12@DatabaseSetup(value = [
13    "/com/testwithspring/master/users.xml",
14    "/com/testwithspring/master/no-tasks-and-tags.xml"
15])
16@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
17@DisplayName("Create a new task")
18class CreateNewTaskTest(
19        @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
20        @Autowired private val repository: TaskRepository
21) {
22
23    companion object {
24
25        private val NEXT_FREE_ID = 1L
26    }
27
28    private val idColumnReset = IdColumnReset(jdbcTemplate)
29
30    private val INPUT = CreateTask(
31            creator = Creator(Tasks.WriteLesson.CREATOR_ID),
32            description = Tasks.WriteLesson.DESCRIPTION,
33            status = Tasks.WriteLesson.STATUS,
34            title = Tasks.WriteLesson.TITLE
35    )
36}

The Tasks class is a constant class which contains the column values found from our DbUnit data sets. Its source code looks as follows:

 1class Tasks {
 2
 3    class WriteLesson {
 4
 5        companion object {
 6
 7            const val ID = 2L;
 8            const val CREATOR_ID = 1L
 9            const val DESCRIPTION = "This lesson talks about integration testing";
10            const val TITLE = "Write lesson"
11            val STATUS = TaskStatus.OPEN
12        }
13    }
14}

Seventh, we have to write a setup function that’s run before a test function is run. Our implementation of this function must reset the auto-increment column of the tasks database table.

After we have implemented this setup function, 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.junit.jupiter.SoftAssertionsExtension
 5import org.junit.jupiter.api.BeforeEach
 6import org.junit.jupiter.api.DisplayName
 7import org.junit.jupiter.api.extension.ExtendWith
 8import org.springframework.beans.factory.annotation.Autowired
 9import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
10
11@DatabaseIntegrationTest
12@ExtendWith(SoftAssertionsExtension::class)
13@DatabaseSetup(value = [
14    "/com/testwithspring/master/users.xml",
15    "/com/testwithspring/master/no-tasks-and-tags.xml"
16])
17@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
18@DisplayName("Create a new task")
19class CreateNewTaskTest(
20        @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
21        @Autowired private val repository: TaskRepository
22) {
23
24    companion object {
25
26        private val NEXT_FREE_ID = 1L
27    }
28
29    private val idColumnReset = IdColumnReset(jdbcTemplate)
30
31    private val INPUT = CreateTask(
32            creator = Creator(Tasks.WriteLesson.CREATOR_ID),
33            description = Tasks.WriteLesson.DESCRIPTION,
34            status = Tasks.WriteLesson.STATUS,
35            title = Tasks.WriteLesson.TITLE
36    )
37
38    @BeforeEach
39    fun resetIdColumn() {
40        idColumnReset.resetIdColumns("tasks")
41    }
42}

We have now configured the system under test. Let’s move on and 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 create() function by following these steps:

First, we have to ensure that the system under test uses the next free id when it inserts a new task into the database. After we have written the required integration test, the source code of our test class looks as follows:

 1import com.github.springtestdbunit.annotation.DatabaseSetup
 2import com.github.springtestdbunit.annotation.DbUnitConfiguration
 3import com.github.springtestdbunit.annotation.ExpectedDatabase
 4import com.github.springtestdbunit.assertion.DatabaseAssertionMode
 5import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
 6import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension
 7import org.junit.jupiter.api.BeforeEach
 8import org.junit.jupiter.api.DisplayName
 9import org.junit.jupiter.api.Nested
10import org.junit.jupiter.api.Test
11import org.junit.jupiter.api.extension.ExtendWith
12import org.springframework.beans.factory.annotation.Autowired
13import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
14
15@DatabaseIntegrationTest
16@ExtendWith(SoftAssertionsExtension::class)
17@DatabaseSetup(value = [
18    "/com/testwithspring/master/users.xml",
19    "/com/testwithspring/master/no-tasks-and-tags.xml"
20])
21@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
22@DisplayName("Create a new task")
23class CreateNewTaskTest(
24        @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
25        @Autowired private val repository: TaskRepository
26) {
27
28    companion object {
29
30        private val NEXT_FREE_ID = 1L
31    }
32
33    private val idColumnReset = IdColumnReset(jdbcTemplate)
34
35    private val INPUT = CreateTask(
36            creator = Creator(Tasks.WriteLesson.CREATOR_ID),
37            description = Tasks.WriteLesson.DESCRIPTION,
38            status = Tasks.WriteLesson.STATUS,
39            title = Tasks.WriteLesson.TITLE
40    )
41
42    @BeforeEach
43    fun resetIdColumn() {
44        idColumnReset.resetIdColumns("tasks")
45    }
46
47    @Test
48    @DisplayName("Should use the next free id when a new task is saved to the database")
49    @ExpectedDatabase(
50            value = "create-task-id-expected.xml",
51            assertionMode = DatabaseAssertionMode.NON_STRICT
52    )
53    fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
54        repository.create(INPUT)
55    }
56}

The DbUnit data set (create-task-id-expected.xml), which ensures that the system under test uses the next free id when it inserts a new task into the database, looks as follows:

1<dataset>
2    <tasks id="1"/>
3</dataset>

Second, we have to verify that the system under test saves the correct lifecycle column values to the database. After we have written our test function, the source code of our test class looks as follows:

 1import com.github.springtestdbunit.annotation.DatabaseSetup
 2import com.github.springtestdbunit.annotation.DbUnitConfiguration
 3import com.github.springtestdbunit.annotation.ExpectedDatabase
 4import com.github.springtestdbunit.assertion.DatabaseAssertionMode
 5import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
 6import org.assertj.core.api.SoftAssertions
 7import org.junit.jupiter.api.BeforeEach
 8import org.junit.jupiter.api.DisplayName
 9import org.junit.jupiter.api.Nested
10import org.junit.jupiter.api.Test
11import org.junit.jupiter.api.extension.ExtendWith
12import org.springframework.beans.factory.annotation.Autowired
13import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
14
15@DatabaseIntegrationTest
16@ExtendWith(SoftAssertionsExtension::class)
17@DatabaseSetup(value = [
18    "/com/testwithspring/master/users.xml",
19    "/com/testwithspring/master/no-tasks-and-tags.xml"
20])
21@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
22@DisplayName("Create a new task")
23class CreateNewTaskTest(
24        @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
25        @Autowired private val repository: TaskRepository
26) {
27
28    companion object {
29
30        private val NEXT_FREE_ID = 1L
31    }
32
33    private val idColumnReset = IdColumnReset(jdbcTemplate)
34
35    private val INPUT = CreateTask(
36            creator = Creator(Tasks.WriteLesson.CREATOR_ID),
37            description = Tasks.WriteLesson.DESCRIPTION,
38            status = Tasks.WriteLesson.STATUS,
39            title = Tasks.WriteLesson.TITLE
40    )
41
42    @BeforeEach
43    fun resetIdColumn() {
44        idColumnReset.resetIdColumns("tasks")
45    }
46
47    @Test
48    @DisplayName("Should use the next free id when a new task is saved to the database")
49    @ExpectedDatabase(
50            value = "create-task-id-expected.xml",
51            assertionMode = DatabaseAssertionMode.NON_STRICT
52    )
53    fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
54        repository.create(INPUT)
55    }
56
57    @Test
58    @DisplayName("Should insert correct lifecycle column values into the database")
59    @ExpectedDatabase(
60            value = "create-task-lifecycle-columns-expected.xml",
61            assertionMode = DatabaseAssertionMode.NON_STRICT
62    )
63    fun shouldInsertCorrectLifecycleColumnValuesIntoDatabase() {
64        repository.create(INPUT)
65    }
66}

The DbUnit data set (create-task-lifecycle-columns-expected.xml), which ensures that the system under test inserts the correct lifecycle column values into the database, looks as follows:

1<dataset>
2    <tasks assignee_id="[null]"
3           closer_id="[null]"
4           creator_id="1"
5           modifier_id="1"/>
6</dataset>

Third, we have to ensure that the system under test inserts the correct status and resolution into the database. After we have written the required integration test, our test class looks as follows:

 1import com.github.springtestdbunit.annotation.DatabaseSetup
 2import com.github.springtestdbunit.annotation.DbUnitConfiguration
 3import com.github.springtestdbunit.annotation.ExpectedDatabase
 4import com.github.springtestdbunit.assertion.DatabaseAssertionMode
 5import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
 6import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension
 7import org.junit.jupiter.api.BeforeEach
 8import org.junit.jupiter.api.DisplayName
 9import org.junit.jupiter.api.Nested
10import org.junit.jupiter.api.Test
11import org.junit.jupiter.api.extension.ExtendWith
12import org.springframework.beans.factory.annotation.Autowired
13import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
14
15@DatabaseIntegrationTest
16@ExtendWith(SoftAssertionsExtension::class)
17@DatabaseSetup(value = [
18    "/com/testwithspring/master/users.xml",
19    "/com/testwithspring/master/no-tasks-and-tags.xml"
20])
21@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
22@DisplayName("Create a new task")
23class CreateNewTaskTest(
24        @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
25        @Autowired private val repository: TaskRepository
26) {
27
28    companion object {
29
30        private val NEXT_FREE_ID = 1L
31    }
32
33    private val idColumnReset = IdColumnReset(jdbcTemplate)
34
35    private val INPUT = CreateTask(
36            creator = Creator(Tasks.WriteLesson.CREATOR_ID),
37            description = Tasks.WriteLesson.DESCRIPTION,
38            status = Tasks.WriteLesson.STATUS,
39            title = Tasks.WriteLesson.TITLE
40    )
41
42    @BeforeEach
43    fun resetIdColumn() {
44        idColumnReset.resetIdColumns("tasks")
45    }
46
47    @Test
48    @DisplayName("Should use the next free id when a new task is saved to the database")
49    @ExpectedDatabase(
50            value = "create-task-id-expected.xml",
51            assertionMode = DatabaseAssertionMode.NON_STRICT
52    )
53    fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
54        repository.create(INPUT)
55    }
56
57    @Test
58    @DisplayName("Should insert correct lifecycle column values into the database")
59    @ExpectedDatabase(
60            value = "create-task-lifecycle-columns-expected.xml",
61            assertionMode = DatabaseAssertionMode.NON_STRICT
62    )
63    fun shouldInsertCorrectLifecycleColumnValuesIntoDatabase() {
64        repository.create(INPUT)
65    }
66
67    @Test
68    @DisplayName("Should save the correct status and resolution to the database")
69    @ExpectedDatabase(
70            value = "create-task-status-expected.xml",
71            assertionMode = DatabaseAssertionMode.NON_STRICT
72    )
73    fun shouldSaveCorrectStatusAndResolutionToDatabase() {
74        repository.create(INPUT)
75    }
76}

The DbUnit data set (create-task-status-expected.xml), which verifies that the system under test saves the correct status and resolution to the database, looks as follows:

1<dataset>
2    <tasks resolution="[null]"
3           status="OPEN"/>
4</dataset>

Fourth, we have to verify that the system under test inserts the correct title and description into the database. After we have written our integration test, the source code of our test class looks as follows:

 1import com.github.springtestdbunit.annotation.DatabaseSetup
 2import com.github.springtestdbunit.annotation.DbUnitConfiguration
 3import com.github.springtestdbunit.annotation.ExpectedDatabase
 4import com.github.springtestdbunit.assertion.DatabaseAssertionMode
 5import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
 6import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension
 7import org.junit.jupiter.api.BeforeEach
 8import org.junit.jupiter.api.DisplayName
 9import org.junit.jupiter.api.Nested
10import org.junit.jupiter.api.Test
11import org.junit.jupiter.api.extension.ExtendWith
12import org.springframework.beans.factory.annotation.Autowired
13import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
14
15@DatabaseIntegrationTest
16@ExtendWith(SoftAssertionsExtension::class)
17@DatabaseSetup(value = [
18    "/com/testwithspring/master/users.xml",
19    "/com/testwithspring/master/no-tasks-and-tags.xml"
20])
21@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
22@DisplayName("Create a new task")
23class CreateNewTaskTest(
24        @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
25        @Autowired private val repository: TaskRepository
26) {
27
28    companion object {
29
30        private val NEXT_FREE_ID = 1L
31    }
32
33    private val idColumnReset = IdColumnReset(jdbcTemplate)
34
35    private val INPUT = CreateTask(
36            creator = Creator(Tasks.WriteLesson.CREATOR_ID),
37            description = Tasks.WriteLesson.DESCRIPTION,
38            status = Tasks.WriteLesson.STATUS,
39            title = Tasks.WriteLesson.TITLE
40    )
41
42    @BeforeEach
43    fun resetIdColumn() {
44        idColumnReset.resetIdColumns("tasks")
45    }
46
47    @Test
48    @DisplayName("Should use the next free id when a new task is saved to the database")
49    @ExpectedDatabase(
50            value = "create-task-id-expected.xml",
51            assertionMode = DatabaseAssertionMode.NON_STRICT
52    )
53    fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
54        repository.create(INPUT)
55    }
56
57    @Test
58    @DisplayName("Should insert correct lifecycle column values into the database")
59    @ExpectedDatabase(
60            value = "create-task-lifecycle-columns-expected.xml",
61            assertionMode = DatabaseAssertionMode.NON_STRICT
62    )
63    fun shouldInsertCorrectLifecycleColumnValuesIntoDatabase() {
64        repository.create(INPUT)
65    }
66
67    @Test
68    @DisplayName("Should save the correct status and resolution to the database")
69    @ExpectedDatabase(
70            value = "create-task-status-expected.xml",
71            assertionMode = DatabaseAssertionMode.NON_STRICT
72    )
73    fun shouldSaveCorrectStatusAndResolutionToDatabase() {
74        repository.create(INPUT)
75    }
76
77    @Test
78    @DisplayName("Should insert the correct title and description into the database")
79    @ExpectedDatabase(
80            value = "create-task-title-description-expected.xml",
81            assertionMode = DatabaseAssertionMode.NON_STRICT
82    )
83    fun shouldInsertCorrectTitleAndDescriptionIntoDatabase() {
84        repository.create(INPUT)
85    }
86}

The DbUnit data set (create-task-title-description-expected.xml), which ensures that the system under test saves the correct title and description to the database, looks as follows:

1<dataset>
2    <tasks description="This lesson talks about integration testing"
3           title="Write lesson"/>
4</dataset>

Fifth, we have to verify that the system under test creates a task that has no tags. After we have written our test function, our test class looks as follows:

 1import com.github.springtestdbunit.annotation.DatabaseSetup
 2import com.github.springtestdbunit.annotation.DbUnitConfiguration
 3import com.github.springtestdbunit.annotation.ExpectedDatabase
 4import com.github.springtestdbunit.assertion.DatabaseAssertionMode
 5import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
 6import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension
 7import org.junit.jupiter.api.BeforeEach
 8import org.junit.jupiter.api.DisplayName
 9import org.junit.jupiter.api.Nested
10import org.junit.jupiter.api.Test
11import org.junit.jupiter.api.extension.ExtendWith
12import org.springframework.beans.factory.annotation.Autowired
13import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
14
15@DatabaseIntegrationTest
16@ExtendWith(SoftAssertionsExtension::class)
17@DatabaseSetup(value = [
18    "/com/testwithspring/master/users.xml",
19    "/com/testwithspring/master/no-tasks-and-tags.xml"
20])
21@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
22@DisplayName("Create a new task")
23class CreateNewTaskTest(
24        @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
25        @Autowired private val repository: TaskRepository
26) {
27
28    companion object {
29
30        private val NEXT_FREE_ID = 1L
31    }
32
33    private val idColumnReset = IdColumnReset(jdbcTemplate)
34
35    private val INPUT = CreateTask(
36            creator = Creator(Tasks.WriteLesson.CREATOR_ID),
37            description = Tasks.WriteLesson.DESCRIPTION,
38            status = Tasks.WriteLesson.STATUS,
39            title = Tasks.WriteLesson.TITLE
40    )
41
42    @BeforeEach
43    fun resetIdColumn() {
44        idColumnReset.resetIdColumns("tasks")
45    }
46
47    @Test
48    @DisplayName("Should use the next free id when a new task is saved to the database")
49    @ExpectedDatabase(
50            value = "create-task-id-expected.xml",
51            assertionMode = DatabaseAssertionMode.NON_STRICT
52    )
53    fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
54        repository.create(INPUT)
55    }
56
57    @Test
58    @DisplayName("Should insert correct lifecycle column values into the database")
59    @ExpectedDatabase(
60            value = "create-task-lifecycle-columns-expected.xml",
61            assertionMode = DatabaseAssertionMode.NON_STRICT
62    )
63    fun shouldInsertCorrectLifecycleColumnValuesIntoDatabase() {
64        repository.create(INPUT)
65    }
66
67    @Test
68    @DisplayName("Should save the correct status and resolution to the database")
69    @ExpectedDatabase(
70            value = "create-task-status-expected.xml",
71            assertionMode = DatabaseAssertionMode.NON_STRICT
72    )
73    fun shouldSaveCorrectStatusAndResolutionToDatabase() {
74        repository.create(INPUT)
75    }
76
77    @Test
78    @DisplayName("Should insert the correct title and description to the database")
79    @ExpectedDatabase(
80            value = "create-task-title-description-expected.xml",
81            assertionMode = DatabaseAssertionMode.NON_STRICT
82    )
83    fun shouldInsertCorrectTitleAndDescriptionToDatabase() {
84        repository.create(INPUT)
85    }
86
87    @Test
88    @DisplayName("Should create a task that has no tags")
89    @ExpectedDatabase(
90            value = "create-task-tags-expected.xml",
91            assertionMode = DatabaseAssertionMode.NON_STRICT
92    )
93    fun shouldCreateTaskThatHasNoTags() {
94        repository.create(INPUT)
95    }
96}

The DbUnit data set (create-task-tags-expected.xml), which verifies that the system under test creates a task that has no tags, looks as follows:

1<dataset>
2    <tags/>
3    <tasks_tags/>
4</dataset>
Before we continue, I want to point out that it’s a good idea to use small DbUnit data sets when we write assertions for the data found from the database because this technique helps to identify the root cause of a failing integration test.

Sixth, we have to ensure that the system under test returns the information of the created task. After we have written our integration test, the source code of our test class looks as follows:

  1import com.github.springtestdbunit.annotation.DatabaseSetup
  2import com.github.springtestdbunit.annotation.DbUnitConfiguration
  3import com.github.springtestdbunit.annotation.ExpectedDatabase
  4import com.github.springtestdbunit.assertion.DatabaseAssertionMode
  5import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
  6import org.assertj.core.api.Assertions.assertThat
  7import org.assertj.core.api.SoftAssertions
  8import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension
  9import org.junit.jupiter.api.BeforeEach
 10import org.junit.jupiter.api.DisplayName
 11import org.junit.jupiter.api.Nested
 12import org.junit.jupiter.api.Test
 13import org.junit.jupiter.api.extension.ExtendWith
 14import org.springframework.beans.factory.annotation.Autowired
 15import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
 16
 17@DatabaseIntegrationTest
 18@ExtendWith(SoftAssertionsExtension::class)
 19@DatabaseSetup(value = [
 20    "/com/testwithspring/master/users.xml",
 21    "/com/testwithspring/master/no-tasks-and-tags.xml"
 22])
 23@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
 24@DisplayName("Create a new task")
 25class CreateNewTaskTest(
 26        @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
 27        @Autowired private val repository: TaskRepository
 28) {
 29
 30    companion object {
 31
 32        private val NEXT_FREE_ID = 1L
 33    }
 34
 35    private val idColumnReset = IdColumnReset(jdbcTemplate)
 36
 37    private val INPUT = CreateTask(
 38            creator = Creator(Tasks.WriteLesson.CREATOR_ID),
 39            description = Tasks.WriteLesson.DESCRIPTION,
 40            status = Tasks.WriteLesson.STATUS,
 41            title = Tasks.WriteLesson.TITLE
 42    )
 43
 44    @BeforeEach
 45    fun resetIdColumn() {
 46        idColumnReset.resetIdColumns("tasks")
 47    }
 48
 49    @Test
 50    @DisplayName("Should use the next free id when a new task is saved to the database")
 51    @ExpectedDatabase(
 52            value = "create-task-id-expected.xml",
 53            assertionMode = DatabaseAssertionMode.NON_STRICT
 54    )
 55    fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
 56        repository.create(INPUT)
 57    }
 58
 59    @Test
 60    @DisplayName("Should insert correct lifecycle column values into the database")
 61    @ExpectedDatabase(
 62            value = "create-task-lifecycle-columns-expected.xml",
 63            assertionMode = DatabaseAssertionMode.NON_STRICT
 64    )
 65    fun shouldInsertCorrectLifecycleColumnValuesIntoDatabase() {
 66        repository.create(INPUT)
 67    }
 68
 69    @Test
 70    @DisplayName("Should save the correct status and resolution to the database")
 71    @ExpectedDatabase(
 72            value = "create-task-status-expected.xml",
 73            assertionMode = DatabaseAssertionMode.NON_STRICT
 74    )
 75    fun shouldSaveCorrectStatusAndResolutionToDatabase() {
 76        repository.create(INPUT)
 77    }
 78
 79    @Test
 80    @DisplayName("Should insert the correct title and description to the database")
 81    @ExpectedDatabase(
 82            value = "create-task-title-description-expected.xml",
 83            assertionMode = DatabaseAssertionMode.NON_STRICT
 84    )
 85    fun shouldInsertCorrectTitleAndDescriptionToDatabase() {
 86        repository.create(INPUT)
 87    }
 88
 89    @Test
 90    @DisplayName("Should create a task that has no tags")
 91    @ExpectedDatabase(
 92            value = "create-task-tags-expected.xml",
 93            assertionMode = DatabaseAssertionMode.NON_STRICT
 94    )
 95    fun shouldCreateTaskThatHasNoTags() {
 96        repository.create(INPUT)
 97    }
 98
 99    @Test
100    @DisplayName("Should return the information of the created task")
101    fun shouldReturnInformationOfCreatedTask(assertions: SoftAssertions) {
102        val created = repository.create(INPUT)
103
104        assertions.assertThat(created.assignee)
105                .`as`("assignee")
106                .isNull()
107        assertions.assertThat(created.closer)
108                .`as`("closer")
109                .isNull()
110        assertions.assertThat(created.creator.id)
111                .`as`("creatorId")
112                .isEqualByComparingTo(Tasks.WriteLesson.CREATOR_ID)
113        assertions.assertThat(created.description)
114                .`as`("description")
115                .isEqualTo(Tasks.WriteLesson.DESCRIPTION)
116        assertions.assertThat(created.id)
117                .`as`("id")
118                .isEqualByComparingTo(NEXT_FREE_ID)
119        assertions.assertThat(created.modifier.id)
120                .`as`("modifierId")
121                .isEqualByComparingTo(Tasks.WriteLesson.CREATOR_ID)
122        assertions.assertThat(created.resolution)
123                .`as`("resolution")
124                .isNull()
125        assertions.assertThat(created.status)
126                .`as`("status")
127                .isEqualTo(Tasks.WriteLesson.STATUS)
128        assertions.assertThat(created.title)
129                .`as`("title")
130                .isEqualTo(Tasks.WriteLesson.TITLE)
131    }
132}
By the way, the SoftAssertions class helps us to reduce the number of integration test functions because it doesn’t fail the test immediately if an assertion fails. Instead, it collects all assertion failures and reports them after the test function has been run. This shortens the feedback loop because we have to run our test function only once and we can still see all assertion failures.

Seventh, we have to verify that the system under test returns a task that has no tags. After we have written the required integration test, the source code of our test class looks as follows:

  1import com.github.springtestdbunit.annotation.DatabaseSetup
  2import com.github.springtestdbunit.annotation.DbUnitConfiguration
  3import com.github.springtestdbunit.annotation.ExpectedDatabase
  4import com.github.springtestdbunit.assertion.DatabaseAssertionMode
  5import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
  6import org.assertj.core.api.Assertions.assertThat
  7import org.assertj.core.api.SoftAssertions
  8import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension
  9import org.junit.jupiter.api.BeforeEach
 10import org.junit.jupiter.api.DisplayName
 11import org.junit.jupiter.api.Nested
 12import org.junit.jupiter.api.Test
 13import org.junit.jupiter.api.extension.ExtendWith
 14import org.springframework.beans.factory.annotation.Autowired
 15import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
 16
 17@DatabaseIntegrationTest
 18@ExtendWith(SoftAssertionsExtension::class)
 19@DatabaseSetup(value = [
 20    "/com/testwithspring/master/users.xml",
 21    "/com/testwithspring/master/no-tasks-and-tags.xml"
 22])
 23@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
 24@DisplayName("Create a new task")
 25class CreateNewTaskTest(
 26        @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
 27        @Autowired private val repository: TaskRepository
 28) {
 29
 30    companion object {
 31
 32        private val NEXT_FREE_ID = 1L
 33    }
 34
 35    private val idColumnReset = IdColumnReset(jdbcTemplate)
 36
 37    private val INPUT = CreateTask(
 38            creator = Creator(Tasks.WriteLesson.CREATOR_ID),
 39            description = Tasks.WriteLesson.DESCRIPTION,
 40            status = Tasks.WriteLesson.STATUS,
 41            title = Tasks.WriteLesson.TITLE
 42    )
 43
 44    @BeforeEach
 45    fun resetIdColumn() {
 46        idColumnReset.resetIdColumns("tasks")
 47    }
 48
 49    @Test
 50    @DisplayName("Should use the next free id when a new task is saved to the database")
 51    @ExpectedDatabase(
 52            value = "create-task-id-expected.xml",
 53            assertionMode = DatabaseAssertionMode.NON_STRICT
 54    )
 55    fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
 56        repository.create(INPUT)
 57    }
 58
 59    @Test
 60    @DisplayName("Should insert correct lifecycle column values into the database")
 61    @ExpectedDatabase(
 62            value = "create-task-lifecycle-columns-expected.xml",
 63            assertionMode = DatabaseAssertionMode.NON_STRICT
 64    )
 65    fun shouldInsertCorrectLifecycleColumnValuesIntoDatabase() {
 66        repository.create(INPUT)
 67    }
 68
 69    @Test
 70    @DisplayName("Should save the correct status and resolution to the database")
 71    @ExpectedDatabase(
 72            value = "create-task-status-expected.xml",
 73            assertionMode = DatabaseAssertionMode.NON_STRICT
 74    )
 75    fun shouldSaveCorrectStatusAndResolutionToDatabase() {
 76        repository.create(INPUT)
 77    }
 78
 79    @Test
 80    @DisplayName("Should insert the correct title and description to the database")
 81    @ExpectedDatabase(
 82            value = "create-task-title-description-expected.xml",
 83            assertionMode = DatabaseAssertionMode.NON_STRICT
 84    )
 85    fun shouldInsertCorrectTitleAndDescriptionToDatabase() {
 86        repository.create(INPUT)
 87    }
 88
 89    @Test
 90    @DisplayName("Should create a task that has no tags")
 91    @ExpectedDatabase(
 92            value = "create-task-tags-expected.xml",
 93            assertionMode = DatabaseAssertionMode.NON_STRICT
 94    )
 95    fun shouldCreateTaskThatHasNoTags() {
 96        repository.create(INPUT)
 97    }
 98
 99    @Test
100    @DisplayName("Should return the information of the created task")
101    fun shouldReturnInformationOfCreatedTask(assertions: SoftAssertions) {
102        val created = repository.create(INPUT)
103
104        assertions.assertThat(created.assignee)
105                .`as`("assignee")
106                .isNull()
107        assertions.assertThat(created.closer)
108                .`as`("closer")
109                .isNull()
110        assertions.assertThat(created.creator.id)
111                .`as`("creatorId")
112                .isEqualByComparingTo(Tasks.WriteLesson.CREATOR_ID)
113        assertions.assertThat(created.description)
114                .`as`("description")
115                .isEqualTo(Tasks.WriteLesson.DESCRIPTION)
116        assertions.assertThat(created.id)
117                .`as`("id")
118                .isEqualByComparingTo(NEXT_FREE_ID)
119        assertions.assertThat(created.modifier.id)
120                .`as`("modifierId")
121                .isEqualByComparingTo(Tasks.WriteLesson.CREATOR_ID)
122        assertions.assertThat(created.resolution)
123                .`as`("resolution")
124                .isNull()
125        assertions.assertThat(created.status)
126                .`as`("status")
127                .isEqualTo(Tasks.WriteLesson.STATUS)
128        assertions.assertThat(created.title)
129                .`as`("title")
130                .isEqualTo(Tasks.WriteLesson.TITLE)
131    }
132
133    @Test
134    @DisplayName("Should return a task that has no tags")
135    fun shouldReturnTaskThatHasNoTags() {
136        val created = repository.create(INPUT)
137        assertThat(created.tags).isEmpty();
138    }
139}

We can now write integration tests for repository functions which write data to the database. Let’s summarize what we learned from this lesson.

Summary

This lesson has taught us two things:

  • It’s a good idea to use small DbUnit data sets when we write assertions for the data found from the database because this technique helps to identify the root cause of a failing integration test.
  • We can reduce the number of required integration test functions and shorten the feedback loop by using soft assertions.

Get the source code from Github