Ao concluirmos esta aula, compreenderemos como configurar o Spring MVC Test framework (MockMvc / MockMvcTester) quando escrevemos testes de integração para uma aplicação web Spring tradicional ou para uma aplicação web que utiliza Spring Boot 4.
Escrevendo Testes de Integração para uma Aplicação Web Spring Framework
Quando escrevemos testes de integração para uma aplicação web no Spring Framework puro (sem Spring Boot), não usamos MockMvcBuilders.standaloneSetup(...) (que é exclusivo para testes unitários isolados). Em vez disso, construímos o MockMvc a partir do WebApplicationContext real carregado pelo Spring Test!
Fazemos essa configuração seguindo quatro passos:
Primeiro, garantimos que o ApplicationContext carregado para o nosso teste de integração seja uma instância de WebApplicationContext. Fazemos isso anotando nossa classe de teste com @WebAppConfiguration:
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
|
package com.testwithspring.intermediate.message
import com.github.springtestdbunit.DbUnitTestExecutionListener
import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.TestExecutionListeners
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener
import org.springframework.test.context.transaction.TransactionalTestExecutionListener
import org.springframework.test.context.web.WebAppConfiguration
@ExtendWith(SpringExtension::class)
@ContextConfiguration(classes = [IntegrationTestContext::class])
@WebAppConfiguration
@TestExecutionListeners(
value = [
DependencyInjectionTestExecutionListener::class,
TransactionalTestExecutionListener::class,
DbUnitTestExecutionListener::class
]
)
@DatabaseSetup("messages.xml")
@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
@Tag("integrationTest")
class GetMessageTest {
}
|
Segundo, se estivermos declarando a lista de listeners manualmente em @TestExecutionListeners (sem MERGE_WITH_DEFAULTS), precisamos registrar o ServletTestExecutionListener. Esse listener configura os objetos mock da API de Servlets (MockServletContext, MockHttpServletRequest, MockHttpSession) necessários para o WebApplicationContext:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@ExtendWith(SpringExtension::class)
@ContextConfiguration(classes = [IntegrationTestContext::class])
@WebAppConfiguration
@TestExecutionListeners(
value = [
DependencyInjectionTestExecutionListener::class,
TransactionalTestExecutionListener::class,
DbUnitTestExecutionListener::class,
ServletTestExecutionListener::class
]
)
@DatabaseSetup("messages.xml")
@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
@Tag("integrationTest")
class GetMessageTest {
}
|
Terceiro, adicionamos dois campos à nossa classe de teste de integração:
webAppContext: Recebe via @Autowired o WebApplicationContext carregado pelo Spring Test.
mockMvc: Armazena a instância de MockMvc que usaremos para enviar requisições HTTP nos testes.
1
2
3
4
5
6
7
|
class GetMessageTest {
@Autowired
private lateinit var webAppContext: WebApplicationContext
private lateinit var mockMvc: MockMvc
}
|
Quarto, criamos um método @BeforeEach que inicializa o campo mockMvc invocando MockMvcBuilders.webAppContextSetup(webAppContext).build():
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
|
package com.testwithspring.intermediate.message
import com.github.springtestdbunit.DbUnitTestExecutionListener
import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.TestExecutionListeners
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener
import org.springframework.test.context.transaction.TransactionalTestExecutionListener
import org.springframework.test.context.web.ServletTestExecutionListener
import org.springframework.test.context.web.WebAppConfiguration
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.setup.MockMvcBuilders
import org.springframework.web.context.WebApplicationContext
@ExtendWith(SpringExtension::class)
@ContextConfiguration(classes = [IntegrationTestContext::class])
@WebAppConfiguration
@TestExecutionListeners(
value = [
DependencyInjectionTestExecutionListener::class,
TransactionalTestExecutionListener::class,
DbUnitTestExecutionListener::class,
ServletTestExecutionListener::class
]
)
@DatabaseSetup("messages.xml")
@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
@Tag("integrationTest")
class GetMessageTest {
@Autowired
private lateinit var webAppContext: WebApplicationContext
private lateinit var mockMvc: MockMvc
@BeforeEach
fun configureSystemUnderTest() {
mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext)
.build()
}
}
|
Pronto! Configuramos o MockMvc conectado a toda a infraestrutura real do WebApplicationContext.
Vamos ver agora como essa configuração fica muito mais enxuta quando utilizamos o Spring Boot 4!
Quando escrevemos testes de integração para uma aplicação web que utiliza o Spring Boot, não precisamos injetar o WebApplicationContext nem criar um método @BeforeEach para chamar MockMvcBuilders.webAppContextSetup(...) manualmente!
Podemos configurar o Spring MVC Test framework seguindo três passos simples:
Primeiro, habilitamos a auto-configuração do MockMvc (e do MockMvcTester) anotando a nossa classe de teste @SpringBootTest com @AutoConfigureMockMvc:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@SpringBootTest(classes = [IntegrationTestContext::class])
@AutoConfigureMockMvc
@TestExecutionListeners(
value = [
DependencyInjectionTestExecutionListener::class,
TransactionalTestExecutionListener::class,
DbUnitTestExecutionListener::class
]
)
@DatabaseSetup("messages.xml")
@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
@Tag("integrationTest")
class GetMessageTest {
}
|
Segundo, garantimos que o ServletTestExecutionListener esteja registrado (ou usamos mergeMode = MergeMode.MERGE_WITH_DEFAULTS, que já mantém o ServletTestExecutionListener e todos os listeners do Spring Boot ativos por padrão!).
Terceiro, injetamos diretamente o bean MockMvc (ou MockMvcTester) gerenciado pelo container do Spring Boot na nossa classe de teste via @Autowired:
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
|
package com.testwithspring.intermediate.message
import com.github.springtestdbunit.DbUnitTestExecutionListener
import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import org.junit.jupiter.api.Tag
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.TestExecutionListeners
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener
import org.springframework.test.context.transaction.TransactionalTestExecutionListener
import org.springframework.test.context.web.ServletTestExecutionListener
import org.springframework.test.web.servlet.MockMvc
@SpringBootTest(classes = [IntegrationTestContext::class])
@AutoConfigureMockMvc
@TestExecutionListeners(
value = [
DependencyInjectionTestExecutionListener::class,
TransactionalTestExecutionListener::class,
DbUnitTestExecutionListener::class,
ServletTestExecutionListener::class
]
)
@DatabaseSetup("messages.xml")
@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
@Tag("integrationTest")
class GetMessageTest(
@Autowired private val mockMvc: MockMvc
) {
}
|
Com @AutoConfigureMockMvc, o próprio Spring Boot constrói o MockMvc (e o MockMvcTester) a partir do WebApplicationContext, registrando automaticamente todos os conversores Jackson, validadores, @ControllerAdvice, filtros customizados e o Spring Security!
Resumo
Esta aula nos ensinou duas coisas fundamentais:
- Ao escrever testes de integração para uma aplicação web Spring Framework pura, anotamos a classe com
@WebAppConfiguration, registramos o ServletTestExecutionListener e construímos o MockMvc em um método @BeforeEach usando MockMvcBuilders.webAppContextSetup(webAppContext).build().
- Ao escrever testes de integração para uma aplicação web Spring Boot, basta anotarmos a classe de teste com
@AutoConfigureMockMvc e injetarmos o MockMvc (ou MockMvcTester) diretamente com @Autowired.