Configurando o ApplicationContext com @SpringBootTest
Ao concluirmos esta aula, nós:
- Seremos capazes de identificar os três principais recursos do suporte a testes de integração do Spring TestContext Framework.
- Saberemos como configurar o
ApplicationContextdos nossos testes de integração ao testar uma aplicação Spring Framework tradicional (@ExtendWith(SpringExtension::class)e@ContextConfiguration). - Seremos capazes de configurar o
ApplicationContextdos nossos testes de integração ao testar uma aplicação Spring Boot 4 (@SpringBootTest).
Os Principais Recursos do Suporte a Testes de Integração do Spring Test Framework
O suporte a testes de integração do Spring Test Framework (Spring TestContext Framework) possui três recursos fundamentais que tornam nossos testes rápidos, limpos e confiáveis:
Primeiro, ele suporta injeção de dependências diretamente nas classes de teste: Isso significa que podemos configurar o sistema sob teste (SUT) utilizando classes de configuração (@Configuration ou @SpringBootApplication) e injetar os beans do Spring diretamente nas nossas classes de teste (via @Autowired ou diretamente via construtor primário no JUnit Jupiter 6 com Kotlin!). Assim, reutilizamos a configuração real da aplicação e evitamos código de inicialização manual repetitivo.
Segundo, ele faz cache dos ApplicationContexts carregados: Se criássemos e inicializássemos todos os componentes do Spring (pool de conexões HikariCP, EntityManagerFactory do Hibernate, filtros web, etc.) do zero antes de cada método de teste, nossa suíte de testes de integração seria extremamente lenta.
O Spring Test resolve esse problema mantendo um cache em memória do ApplicationContext após carregá-lo pela primeira vez. Quando outras classes de teste de integração solicitam a mesma configuração de contexto, o Spring Test reutiliza instantaneamente o ApplicationContext em cache!
Terceiro, ele fornece suporte integrado a gerenciamento de transações: Quando escrevemos testes que interagem com um armazenamento persistente como um banco de dados relacional, precisamos resolver dois problemas:
- Garantir que as alterações feitas no banco de dados durante um teste não afetem os testes subsequentes.
- Executar operações que exigem uma transação ativa (como métodos anotados com
@Transactionalou oEntityManagerdo JPA).
Por padrão, quando anotamos um teste de integração com @Transactional, o Spring Test inicia uma transação antes do método de teste e realiza o rollback automático ao final do teste!
Introdução ao Sistema Sob Teste (SUT)
Nosso sistema sob teste de exemplo consiste em duas classes simples:
Primeiro, a classe MessageRepository anotada com @Repository, que possui um único método chamado findMessage() que retorna a string "Hello World!":
|
|
Segundo, a classe MessageService anotada com @Service, que recebe o MessageRepository via injeção de dependência no construtor e expõe o método getMessage():
|
|
Estamos prontos para configurar o ApplicationContext do nosso primeiro teste de integração!
Configurando o ApplicationContext dos Nossos Testes de Integração
Vamos aprender como configurar o ApplicationContext dos nossos testes de integração em dois cenários: usando o Spring Framework puro e usando o Spring Boot 4.
1. Usando o Spring Framework (@ExtendWith(SpringExtension::class) + @ContextConfiguration)
Se estivermos escrevendo testes de integração para uma aplicação que utiliza o Spring Framework puro (sem Spring Boot), configuramos o ApplicationContext seguindo dois passos:
Primeiro, garantimos que nossa classe de teste seja executada com a extensão do Spring para o JUnit Jupiter 6. No antigo JUnit 4, usávamos @RunWith(SpringJUnit4ClassRunner::class). No JUnit Jupiter 6, usamos @ExtendWith(SpringExtension::class):
|
|
Segundo, especificamos as classes de configuração (@Configuration) que definem os beans do nosso ApplicationContext anotando nossa classe de teste com @ContextConfiguration:
|
|
Dica de Atalho com
@SpringJUnitConfig: No Spring Framework moderno (5.0+ até o Spring Framework 7), a anotação composta@SpringJUnitConfig(classes = [MessageContext::class])combina@ExtendWith(SpringExtension::class)e@ContextConfigurationem uma única anotação!
Onde MessageContext é uma classe de configuração simples que habilita o escaneamento de componentes (@ComponentScan):
|
|
(Nota: Caso você trabalhe em um sistema legado que ainda utilize arquivos XML de configuração do Spring, basta usar o atributo locations de @ContextConfiguration(locations = ["classpath:message-context.xml"])).
Agora podemos injetar o bean MessageService na nossa classe de teste e verificar que o método getMessage() retorna "Hello World!":
|
|
Injeção por Construtor em Testes Kotlin: Graças à integração do
SpringExtensioncom oParameterResolverdo JUnit Jupiter 6, em Kotlin podemos declarar@Autowired private val service: MessageServicediretamente no construtor da classe de teste (ou configurarspring.test.constructor.autowire.mode=allnojunit-platform.propertiespara nem precisar escrever@Autowired!), evitando propriedadeslateinit varmutáveis!
2. Usando o Spring Boot 4 (@SpringBootTest)
Se estivermos escrevendo testes de integração para uma aplicação Spring Boot, devemos substituir @ContextConfiguration pela anotação @SpringBootTest.
Por que usar @SpringBootTest?
- Ela carrega o contexto completo do Spring Boot (incluindo auto-configurações,
application.properties/application.yml, conversores de tipos e infraestrutura de servidor web se necessário). - No JUnit Jupiter 6, a própria anotação
@SpringBootTestjá inclui@ExtendWith(SpringExtension::class)como meta-anotação! Portanto, você não precisa repetir@ExtendWith(SpringExtension::class)no topo da classe.
Veja como fica a nossa classe de teste GetMessageTest usando @SpringBootTest:
|
|
Onde MessageApplication é a classe principal da nossa aplicação Spring Boot:
|
|
(Nota: Se a sua classe de teste estiver no mesmo pacote ou em um subpacote de MessageApplication, você pode escrever apenas @SpringBootTest sem especificar classes = [...], pois o Spring Boot localiza automaticamente a classe anotada com @SpringBootApplication!).
Resumo
Esta aula nos ensinou três conceitos fundamentais:
- No JUnit Jupiter 6, a extensão
SpringExtension(@ExtendWith(SpringExtension::class)) substitui o antigoSpringJUnit4ClassRunnere habilita injeção de dependências, cache de contexto e gerenciamento de transações nos nossos testes. - Ao testar uma aplicação que usa o Spring Framework puro, configuramos as classes
@Configurationou arquivos XML usando@ContextConfiguration(ou@SpringJUnitConfig). - Ao testar uma aplicação que usa o Spring Boot, utilizamos a anotação
@SpringBootTest, que já registra oSpringExtensionautomaticamente e carrega os recursos específicos do Spring Boot.