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.
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 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
jdbcTemplateproperty contains aNamedParameterJdbcTemplateobject which allows us to reset the auto-increment column of thetasksdatabase table. - The
repositoryproperty contains the testedTaskRepositoryobject.
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}
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>
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}
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.