Depois de concluirmos esta aula (baseada no webinar bônus), nós:
- Entenderemos como podemos iniciar e parar um contêiner Docker que executa um banco de dados PostgreSQL quando estamos escrevendo testes de integração para uma aplicação Spring Boot 4.1.2.
- Saberemos como podemos configurar a nossa conexão com o banco de dados (tanto via URL JDBC do Testcontainers quanto com
@ServiceConnection e @DynamicPropertySource no Spring Boot 4).
- Seremos capazes de escrever testes de integração para uma aplicação web Spring Boot usando Kotlin 2.4.21 e JUnit Jupiter 6.0.3.
Esta aula assume que:
- Você está familiarizado com o DbUnit e preparação de estado de banco de dados em testes
- Você sabe configurar seus testes de integração na suíte de testes
- Você sabe escrever testes de integração para aplicações web Spring
- Você está familiarizado com o JUnit Jupiter 6.0.3 (evolução moderna do JUnit 5)
- Você está familiarizado com o Testcontainers 2.0.5
- Você sabe integrar o Testcontainers ao JUnit Jupiter
Na aula anterior, vimos como podemos implementar uma extensão customizada do JUnit Jupiter (ou usar @Testcontainers e @Container) que inicia e para o nosso contêiner Docker. No entanto, se estivermos escrevendo testes de integração para uma aplicação Spring Boot 4.1.2, não precisamos usar uma extensão customizada do JUnit para gerenciar manualmente o ciclo de vida e a ligação das propriedades do DataSource.
Se quisermos iniciar e parar um contêiner Docker que executa um banco de dados PostgreSQL em uma aplicação Spring Boot, precisamos seguir estes passos:
Primeiro, precisamos garantir que o nosso classpath de teste no Gradle 9.8.2 (build.gradle.kts) contenha o Testcontainers 2.0.5 (org.testcontainers:postgresql, org.testcontainers:jdbc), o suporte oficial do Spring Boot para Testcontainers (org.springframework.boot:spring-boot-testcontainers) e o driver JDBC do PostgreSQL (org.postgresql:postgresql).
Se você não sabe como adicionar essas dependências ao seu classpath no build.gradle.kts, consulte a aula de introdução ao Testcontainers neste mesmo módulo.
Segundo, precisamos configurar a nossa conexão com o banco de dados. Podemos fazer isso diretamente no nosso arquivo de propriedades (application-integrationTest.properties) — ou de forma programática no Spring Boot 4.1.2 com @ServiceConnection / @DynamicPropertySource.
Para configurar a conexão através do arquivo de propriedades (application-integrationTest.properties), realizamos as seguintes alterações:
- Configurar a URL JDBC utilizando o seguinte formato:
jdbc:tc:postgresql:[database_version]://[host]:[port]/[database name].
- Garantir que a nossa aplicação Spring Boot utilize o driver JDBC fornecido pelo Testcontainers (
org.testcontainers.jdbc.ContainerDatabaseDriver).
- Configurar o nome de usuário (
username) e a senha (password) do usuário do banco de dados. É importante entender que não precisamos usar um nome de usuário e senha específicos pré-existentes, pois o Testcontainers cria o banco de dados (e o usuário do banco de dados) utilizando os valores das propriedades encontradas no nosso arquivo de propriedades.
Depois de configurarmos a nossa conexão com o banco de dados, o arquivo application-integrationTest.properties fica com a seguinte aparência:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#Database Configuration
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.url=jdbc:tc:postgresql:17.2://localhost/testwithspring
spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver
spring.datasource.username=username
spring.datasource.password=password
#Hibernate Configuration
spring.jpa.database=POSTGRESQL
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.jdbc.time_zone=UTC
#I18N
spring.messages.basename=i18n/messages
#Jackson
spring.jackson.serialization.write-dates-as-timestamps=false
spring.jackson.serialization.write-dates-with-zone-id=true
#Liquibase Configuration
spring.liquibase.change-log=classpath:/db/changelog/db.changelog.xml
spring.liquibase.contexts=integration-test
|
Alternativa Moderna no Spring Boot 4.1.2: @ServiceConnection e @DynamicPropertySource
No Spring Boot 4.1.2, além do suporte via URL JDBC (jdbc:tc:postgresql:...), podemos declarar o contêiner como um bean em uma classe @TestConfiguration anotada com @ServiceConnection. Com @ServiceConnection, o Spring Boot descobre automaticamente a porta mapeada, a URL JDBC, o usuário e a senha diretamente do PostgreSQLContainer do Testcontainers 2.0.5, sem precisar do ContainerDatabaseDriver no arquivo .properties:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package com.testwithspring.master.config
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.testcontainers.service.connection.ServiceConnection
import org.springframework.context.annotation.Bean
import org.testcontainers.containers.PostgreSQLContainer
import org.testcontainers.utility.DockerImageName
@TestConfiguration(proxyBeanMethods = false)
class ContainersConfig {
@Bean
@ServiceConnection
fun postgresContainer(): PostgreSQLContainer<*> {
return PostgreSQLContainer(DockerImageName.parse("postgres:17.2-alpine"))
.withDatabaseName("testwithspring")
.withUsername("username")
.withPassword("password")
}
}
|
E caso precisemos sobrescrever propriedades customizadas adicionais em tempo de execução a partir do contêiner, o Spring Framework fornece a anotação @DynamicPropertySource:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
companion object {
@Container
@ServiceConnection
@JvmStatic
val postgres = PostgreSQLContainer(DockerImageName.parse("postgres:17.2-alpine"))
@DynamicPropertySource
@JvmStatic
fun registerPgProperties(registry: DynamicPropertyRegistry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl)
registry.add("spring.datasource.username", postgres::getUsername)
registry.add("spring.datasource.password", postgres::getPassword)
}
}
|
Informações Adicionais:
Depois de termos adicionado as dependências necessárias ao nosso classpath e configurado a nossa conexão com o banco de dados (seja via URL JDBC jdbc:tc: ou via @ServiceConnection), o Testcontainers inicia o contêiner Docker que executa o banco de dados PostgreSQL quando o contexto da aplicação (ApplicationContext) da nossa aplicação Spring Boot é carregado e o interrompe quando o contexto da aplicação é fechado.
A seguir, descobriremos como podemos configurar o contexto da aplicação dos nossos testes de integração.
Configurando o Contexto da Aplicação dos Nossos Testes de Integração
Podemos configurar o contexto da aplicação dos nossos testes de integração seguindo estes passos:
Primeiro, precisamos criar um TestExecutionListener customizado que estende a classe DbUnitTestExecutionListener. Este TestExecutionListener garante que o DbUnitTestExecutionListener seja invocado antes do WithSecurityContextTestExecutionListener. Precisamos fazer isso porque devemos inserir os dados necessários de conta de usuário no nosso banco de dados antes que o contexto de segurança seja criado pelo objeto WithSecurityContextTestExecutionListener.
Depois de escrevermos o nosso TestExecutionListener customizado em Kotlin, o seu código-fonte fica com a seguinte aparência:
1
2
3
4
5
6
7
8
9
10
|
package com.testwithspring.master
import com.github.springtestdbunit.DbUnitTestExecutionListener
class OrderedDbUnitTestExecutionListener : DbUnitTestExecutionListener() {
override fun getOrder(): Int {
return 9999
}
}
|
Segundo, precisamos escrever uma anotação customizada que nos ajude a eliminar configurações duplicadas quando estivermos escrevendo testes de integração para repositórios gerenciados pelo Spring. Esta anotação integra o framework Spring TestContext com o JUnit Jupiter 6.0.3, habilita o suporte de testes do Spring Boot, configura a classe de configuração do contexto da aplicação carregada, registra o nosso TestExecutionListener customizado, configura o perfil ativo do Spring e configura a tag do JUnit Jupiter que identifica os nossos testes de integração.
Depois de escrevermos a nossa anotação customizada em Kotlin, o seu código-fonte fica com a seguinte aparência:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
package com.testwithspring.master
import com.testwithspring.master.config.Profiles
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.core.annotation.AliasFor
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.TestExecutionListeners
import org.springframework.test.context.junit.jupiter.SpringExtension
import kotlin.reflect.KClass
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@ExtendWith(SpringExtension::class)
@SpringBootTest
@TestExecutionListeners(
value = [OrderedDbUnitTestExecutionListener::class],
mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
)
@ActiveProfiles(Profiles.INTEGRATION_TEST)
@Tag(TestTags.INTEGRATION_TEST)
annotation class RepositoryIntegrationTest(
@get:AliasFor(annotation = SpringBootTest::class)
val classes: Array<KClass<*>> = []
)
|
Terceiro, precisamos escrever uma anotação customizada que nos ajude a eliminar configurações duplicadas quando estivermos escrevendo testes de integração para controladores Spring MVC. Esta anotação integra o framework Spring TestContext com o JUnit Jupiter 6.0.3, habilita o suporte de testes do Spring Boot, configura a classe de configuração do contexto da aplicação carregada, habilita a autoconfiguração do framework Spring MVC Test (@AutoConfigureMockMvc), registra os listeners de execução de teste necessários, configura o perfil ativo do Spring e configura a tag do JUnit Jupiter que identifica os nossos testes de integração.
Depois de escrevermos a nossa anotação customizada em Kotlin, o seu código-fonte fica com a seguinte aparência:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
package com.testwithspring.master
import com.testwithspring.master.config.Profiles
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.core.annotation.AliasFor
import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.TestExecutionListeners
import org.springframework.test.context.junit.jupiter.SpringExtension
import kotlin.reflect.KClass
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@ExtendWith(SpringExtension::class)
@SpringBootTest
@AutoConfigureMockMvc
@TestExecutionListeners(
value = [
OrderedDbUnitTestExecutionListener::class,
WithSecurityContextTestExecutionListener::class
],
mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
)
@ActiveProfiles(Profiles.INTEGRATION_TEST)
@Tag(TestTags.INTEGRATION_TEST)
annotation class WebIntegrationTest(
@get:AliasFor(annotation = SpringBootTest::class)
val classes: Array<KClass<*>> = []
)
|
Informações Adicionais:
- Veja as aulas anteriores sobre Escrevendo Testes Unitários, de Integração e End-to-End com JUnit e Escrevendo Testes de Integração com JUnit para mais detalhes sobre meta-anotações e organização de categorias com
@Tag.
Agora estamos prontos para escrever testes de integração para a nossa aplicação Spring Boot. Vamos avançar e descobrir como podemos escrever testes de integração para repositórios gerenciados pelo Spring.
Escrevendo Testes de Integração para Repositórios Spring
Quando queremos escrever testes de integração para um método de repositório que retorna as informações de todas as tarefas (tasks) encontradas no nosso banco de dados, precisamos seguir estes passos:
Primeiro, precisamos configurar o sistema sob teste (SUT) seguindo estas etapas:
- Anotar a nossa classe de teste com a anotação
@RepositoryIntegrationTest e especificar a classe de configuração que configura o contexto da aplicação do sistema sob teste (IntegrationTestContext::class).
- Inserir os dados de teste necessários no nosso banco de dados. O dataset users.xml contém as contas de usuário da nossa aplicação e o dataset tasks.xml contém as tarefas encontradas no nosso banco de dados.
- Garantir que os nossos datasets do DbUnit sejam carregados pela classe
ReplacementDataSetLoader.
- Adicionar uma propriedade
repository do tipo TaskRepository à nossa classe de teste e garantir que o objeto de repositório testado seja injetado nessa propriedade pelo contêiner do Spring.
Depois de termos configurado o sistema sob teste, o código-fonte da nossa classe de teste fica com a seguinte aparência:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package com.testwithspring.master.task
import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.IntegrationTestContext
import com.testwithspring.master.RepositoryIntegrationTest
import org.junit.jupiter.api.DisplayName
import org.springframework.beans.factory.annotation.Autowired
@RepositoryIntegrationTest(classes = [IntegrationTestContext::class])
@DatabaseSetup(
"/com/testwithspring/master/users.xml",
"/com/testwithspring/master/tasks.xml"
)
@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
@DisplayName("Find all tasks from the database when two tasks are found")
class FindAllTasksWhenTwoTasksAreFoundTest {
@Autowired
private lateinit var repository: TaskRepository
}
|
Segundo, precisamos escrever três testes de integração que verificam se o sistema sob teste está funcionando conforme o esperado quando duas tarefas são encontradas no banco de dados. Depois de termos escrito esses testes de integração, o código-fonte da nossa classe de teste fica com a seguinte aparência:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
package com.testwithspring.master.task
import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.IntegrationTestContext
import com.testwithspring.master.RepositoryIntegrationTest
import com.testwithspring.master.Tasks
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
@RepositoryIntegrationTest(classes = [IntegrationTestContext::class])
@DatabaseSetup(
"/com/testwithspring/master/users.xml",
"/com/testwithspring/master/tasks.xml"
)
@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
@DisplayName("Find all tasks from the database when two tasks are found")
class FindAllTasksWhenTwoTasksAreFoundTest {
@Autowired
private lateinit var repository: TaskRepository
@Test
@DisplayName("Should return two tasks")
fun shouldReturnTwoTasks() {
val tasks: List<TaskListDTO> = repository.findAll()
assertThat(tasks).hasSize(2)
}
@Test
@DisplayName("Should return the correct information of the first task")
fun shouldReturnCorrectInformationOfFirstTask() {
val first: TaskListDTO = repository.findAll()[0]
assertThat(first.id)
.isEqualByComparingTo(Tasks.WriteExampleApp.ID)
assertThat(first.status)
.isEqualTo(Tasks.WriteExampleApp.STATUS)
assertThat(first.title)
.isEqualTo(Tasks.WriteExampleApp.TITLE)
}
@Test
@DisplayName("Should return the correct information of the second task")
fun shouldReturnCorrectInformationOfSecondTask() {
val second: TaskListDTO = repository.findAll()[1]
assertThat(second.id)
.isEqualByComparingTo(Tasks.WriteLesson.ID)
assertThat(second.status)
.isEqualTo(Tasks.WriteLesson.STATUS)
assertThat(second.title)
.isEqualTo(Tasks.WriteLesson.TITLE)
}
}
|
Agora já sabemos como escrever testes de integração para repositórios gerenciados pelo Spring. A seguir, descobriremos como podemos escrever testes de integração para controladores Spring MVC.
Escrevendo Testes de Integração para Controladores Spring MVC
Quando queremos escrever testes de integração para um método de controlador que retorna as informações de uma única tarefa, precisamos seguir estes passos:
Primeiro, precisamos configurar o sistema sob teste seguindo estas etapas:
- Anotar a nossa classe de teste com a anotação
@WebIntegrationTest e especificar a classe de configuração que configura o contexto da aplicação do sistema sob teste (IntegrationTestContext::class).
- Inserir os dados de teste necessários no nosso banco de dados. Utilizaremos os mesmos datasets que usamos no exemplo anterior.
- Garantir que os nossos datasets do DbUnit sejam carregados pela classe
ReplacementDataSetLoader.
- Adicionar uma propriedade
mockMvc do tipo MockMvc à nossa classe de teste e garantir que o objeto MockMvc configurado seja injetado nessa propriedade pelo contêiner do Spring.
Depois de termos configurado o sistema sob teste, o código-fonte da nossa classe de teste fica com a seguinte aparência:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package com.testwithspring.master.web
import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.IntegrationTestContext
import com.testwithspring.master.WebIntegrationTest
import org.junit.jupiter.api.DisplayName
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.web.servlet.MockMvc
@WebIntegrationTest(classes = [IntegrationTestContext::class])
@DatabaseSetup(
"/com/testwithspring/master/users.xml",
"/com/testwithspring/master/tasks.xml"
)
@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
@DisplayName("Find task as a user when the requested task is found")
class FindTaskAsUserWhenTaskIsFoundTest {
@Autowired
private lateinit var mockMvc: MockMvc
}
|
Segundo, precisamos escrever cinco testes de integração que garantem que o sistema sob teste está funcionando conforme o esperado quando um usuário “normal” obtém as informações de uma tarefa existente. Depois de termos escrito esses testes de integração, o código-fonte da nossa classe de teste fica com a seguinte aparência:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
|
package com.testwithspring.master.web
import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.IntegrationTestContext
import com.testwithspring.master.Tasks
import com.testwithspring.master.Users
import com.testwithspring.master.WebIntegrationTest
import org.hamcrest.Matchers.hasSize
import org.hamcrest.Matchers.`is`
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.http.MediaType
import org.springframework.security.test.context.support.WithUserDetails
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.ResultActions
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
@WebIntegrationTest(classes = [IntegrationTestContext::class])
@AutoConfigureMockMvc
@DatabaseSetup(
"/com/testwithspring/master/users.xml",
"/com/testwithspring/master/tasks.xml"
)
@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
@DisplayName("Find task as a user when the requested task is found")
class FindTaskAsUserWhenTaskIsFoundTest {
@Autowired
private lateinit var mockMvc: MockMvc
@Test
@WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
@DisplayName("Should return the HTTP status code OK")
fun shouldReturnHttpStatusCodeOk() {
findTask()
.andExpect(status().isOk)
}
@Test
@WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
@DisplayName("Should return the found task as JSON")
fun shouldReturnFoundTaskAsJson() {
findTask()
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
}
@Test
@WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
@DisplayName("Should return the information of the found task")
fun shouldReturnInformationOfFoundTask() {
findTask()
.andExpect(
jsonPath(
WebTestConstants.JsonPathProperty.Task.Assignee.ID,
`is`(Tasks.WriteExampleApp.Assignee.ID.toInt())
)
)
.andExpect(
jsonPath(
WebTestConstants.JsonPathProperty.Task.Assignee.NAME,
`is`(Tasks.WriteExampleApp.Assignee.NAME)
)
)
.andExpect(
jsonPath(
WebTestConstants.JsonPathProperty.Task.Closer.ID,
`is`(Tasks.WriteExampleApp.Closer.ID.toInt())
)
)
.andExpect(
jsonPath(
WebTestConstants.JsonPathProperty.Task.Closer.NAME,
`is`(Tasks.WriteExampleApp.Closer.NAME)
)
)
.andExpect(
jsonPath(
WebTestConstants.JsonPathProperty.Task.CREATION_TIME,
`is`(Tasks.WriteExampleApp.CREATION_TIME_STRING)
)
)
.andExpect(
jsonPath(
WebTestConstants.JsonPathProperty.Task.Creator.ID,
`is`(Tasks.WriteExampleApp.Creator.ID.toInt())
)
)
.andExpect(
jsonPath(
WebTestConstants.JsonPathProperty.Task.Creator.NAME,
`is`(Tasks.WriteExampleApp.Creator.NAME)
)
)
.andExpect(
jsonPath(
WebTestConstants.JsonPathProperty.Task.ID,
`is`(Tasks.WriteExampleApp.ID.toInt())
)
)
.andExpect(
jsonPath(
WebTestConstants.JsonPathProperty.Task.MODIFICATION_TIME,
`is`(Tasks.WriteExampleApp.MODIFICATION_TIME_STRING)
)
)
.andExpect(
jsonPath(
WebTestConstants.JsonPathProperty.Task.Modifier.ID,
`is`(Tasks.WriteExampleApp.Modifier.ID.toInt())
)
)
.andExpect(
jsonPath(
WebTestConstants.JsonPathProperty.Task.Modifier.NAME,
`is`(Tasks.WriteExampleApp.Modifier.NAME)
)
)
.andExpect(
jsonPath(
WebTestConstants.JsonPathProperty.Task.TITLE,
`is`(Tasks.WriteExampleApp.TITLE)
)
)
.andExpect(
jsonPath(
WebTestConstants.JsonPathProperty.Task.DESCRIPTION,
`is`(Tasks.WriteExampleApp.DESCRIPTION)
)
)
.andExpect(
jsonPath(
WebTestConstants.JsonPathProperty.Task.STATUS,
`is`(Tasks.WriteExampleApp.STATUS.toString())
)
)
.andExpect(
jsonPath(
WebTestConstants.JsonPathProperty.Task.RESOLUTION,
`is`(Tasks.WriteExampleApp.RESOLUTION.toString())
)
)
}
@Test
@WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
@DisplayName("Should return a task that has one tag")
fun shouldReturnTaskThatHasOneTag() {
findTask()
.andExpect(
jsonPath(
WebTestConstants.JsonPathProperty.Task.TAGS,
hasSize<Any>(1)
)
)
}
@Test
@WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
@DisplayName("Should return the information of the correct tag")
fun shouldReturnInformationOfCorrectTag() {
findTask()
.andExpect(
jsonPath(
"$.tags[0].id",
`is`(Tasks.WriteExampleApp.Tags.Example.ID.toInt())
)
)
.andExpect(
jsonPath(
"$.tags[0].name",
`is`(Tasks.WriteExampleApp.Tags.Example.NAME)
)
)
}
private fun findTask(): ResultActions {
return mockMvc.perform(get("/api/task/{taskId}", Tasks.WriteExampleApp.ID))
}
}
|
Agora sabemos como escrever testes de integração para aplicações Spring Boot utilizando o Testcontainers 2.0.5 e o JUnit Jupiter 6.0.3. Vamos resumir o que aprendemos nesta aula.
Resumo
Esta aula nos ensinou quatro coisas fundamentais:
- Se quisermos iniciar e parar um contêiner Docker que executa um banco de dados PostgreSQL, precisamos garantir que o nosso classpath contenha tanto o Testcontainers (incluindo
spring-boot-testcontainers no Spring Boot 4.1.2) quanto o driver JDBC do PostgreSQL.
- Na abordagem via propriedades, o valor da propriedade
spring.datasource.url deve conter uma URL JDBC especial (jdbc:tc:postgresql:...) que configura o banco de dados iniciado, ou podemos declarar um bean PostgreSQLContainer anotado com @ServiceConnection (ou @DynamicPropertySource) para que o Spring Boot 4 configure a conexão automaticamente.
- Ao utilizar a URL JDBC do Testcontainers, o valor da propriedade
spring.datasource.driver-class-name deve conter o nome totalmente qualificado da classe do driver JDBC fornecido pelo Testcontainers (org.testcontainers.jdbc.ContainerDatabaseDriver).
- Depois de integrarmos o Testcontainers ao framework Spring TestContext, o Testcontainers inicia o contêiner Docker quando o contexto da aplicação da nossa aplicação é carregado e o interrompe automaticamente quando o contexto da aplicação é fechado.