Customizando o Contexto com @TestConfiguration e Profiles
Ao concluirmos esta aula, nós:
- Compreenderemos por que é uma excelente prática utilizar configurações e beans customizados quando executamos nossos testes de integração.
- Saberemos como utilizar diferentes arquivos de propriedades e diferentes beans do Spring em ambientes distintos (produção vs testes de integração).
- Seremos capazes de especificar o perfil ativo do Spring (
@ActiveProfiles) ao escrever testes de integração para nossa aplicação.
Por Que Customizar o Contexto nos Testes de Integração?
É muito comum que nossa aplicação utilize configurações diferentes em cada ambiente (desenvolvimento, homologação e produção). Quando escrevemos testes de integração, nosso objetivo ao customizar a configuração do ApplicationContext é substituir certos componentes reais de infraestrutura externa por componentes controlados que tornem nossos testes mais rápidos, determinísticos e fáceis de escrever.
Por exemplo, frequentemente queremos:
- Tornar nossos testes o mais rápidos e isolados possível: Externalizando a configuração de banco de dados para apontar para um container efêmero do Testcontainers (ou um banco em memória).
- Eliminar chamadas HTTP reais para serviços externos de terceiros: Não queremos que nossos testes de integração falhem porque a API de pagamentos ou de envio de e-mails de um fornecedor externo está fora do ar. Podemos resolver isso substituindo o cliente HTTP externo por um stub (ou apontando-o para um servidor WireMock local).
- Congelar o tempo atual (
Clock/DateTimeService): Testar regras de negócio que dependem da data e hora atual (Instant.now()) usando o relógio real do sistema operacional é receita certa para testes frágeis. Ao encapsularmos o relógio em um bean do Spring (ClockouDateTimeService), podemos substituí-lo por um stub que retorna um instante fixo e determinístico durante os testes de integração!
Vamos aprender duas técnicas complementares para customizar o nosso ApplicationContext: perfis do Spring (@Profile + @ActiveProfiles) e @TestConfiguration.
Configurando os Perfis Disponíveis (object Profiles)
Quando utilizamos perfis do Spring (Spring profiles) para separar configurações de produção e de teste, a primeira boa prática é criar um object de constantes em Kotlin com os nomes dos perfis. Isso evita erros de digitação (typos) em strings mágicas espalhadas pelo código.
Nesta aula utilizaremos dois perfis:
application: Utilizado quando executamos nossa aplicação normalmente.integrationTest: Utilizado quando executamos nossos testes de integração.
Veja como fica o nosso object Profiles:
|
|
Vamos descobrir agora como carregar arquivos .properties diferentes dependendo do perfil ativo!
Usando Arquivos de Propriedades Diferentes em Cada Ambiente
Tanto o Spring Framework puro quanto o Spring Boot oferecem suporte nativo para carregar arquivos de propriedades específicos por ambiente. Vamos examinar os dois casos.
1. Usando o Spring Framework Puro (@Profile + @PropertySource)
Em uma aplicação Spring Framework tradicional (sem Spring Boot), podemos configurar nossa classe principal @Configuration (ExampleApplicationContext) para:
- Ler o arquivo
classpath:application.propertiesquando o perfilapplicationestiver ativo. - Ler o arquivo
classpath:integration-test.propertiesquando o perfilintegrationTestestiver ativo.
Fazemos isso declarando duas classes internas anotadas com @Configuration, @Profile e @PropertySource:
|
|
2. Usando o Spring Boot 4 (application-{profile}.properties / .yml)
Se estivermos usando o Spring Boot, não precisamos escrever nenhuma classe @PropertySource! O Spring Boot carrega automaticamente arquivos de configuração específicos do perfil ativo seguindo a convenção de nomenclatura:
application-{profile}.properties(ouapplication-{profile}.yml) no diretóriosrc/main/resourcesousrc/integration-test/resources.
Em outras palavras, usando os nossos dois perfis:
- Quando o perfil
applicationestiver ativo, o Spring Boot lerá as propriedades deapplication-application.properties(além doapplication.propertiesbase). - Quando o perfil
integrationTestestiver ativo nos nossos testes, o Spring Boot lerá e sobrescreverá as propriedades a partir do arquivoapplication-integrationTest.properties!
Usando Beans Diferentes em Ambientes Diferentes
Suponha que nossa aplicação possua a seguinte classe MessageService:
|
|
Se quisermos que o container do Spring injete uma instância de MessageService com a mensagem "Hello World!" quando a aplicação estiver rodando (Profiles.APPLICATION) e uma instância com a mensagem "Hello Test!" durante os testes de integração (Profiles.INTEGRATION_TEST), podemos usar a anotação @Profile nos métodos @Bean:
|
|
Alternativa com
@TestConfigurationno Spring Boot: Além de@Profile, o Spring Boot oferece a anotação@TestConfiguration(no pacotesrc/integration-test/kotlin). Classes anotadas com@TestConfigurationnão são escaneadas automaticamente pelo@SpringBootApplicationde produção, podendo ser importadas sob demanda nos testes via@Import(MyTestConfig::class)com@Primarypara substituir beans específicos (como umClock.fixed(...)ou um stub de serviço externo) sem poluir as classes de configuração de produção!
Especificando o Perfil Ativo nos Testes de Integração (@ActiveProfiles)
Como informamos ao Spring Test Framework qual perfil deve ser ativado ao carregar o ApplicationContext do nosso teste de integração?
Basta anotarmos nossa classe de teste com @ActiveProfiles(Profiles.INTEGRATION_TEST):
|
|
A anotação @ActiveProfiles funciona tanto em aplicações Spring Framework puras quanto em aplicações Spring Boot!
Resumo
Esta aula nos ensinou quatro coisas fundamentais:
- É uma excelente prática substituir componentes externos ou não-determinísticos (como relógio do sistema
Clockou clientes HTTP externos) por implementações controladas nos testes de integração. - Podemos carregar arquivos de propriedades diferentes por ambiente usando
@Profile+@PropertySourceno Spring Framework, ou usando a convenção automáticaapplication-{profile}.propertiesno Spring Boot. - Podemos registrar beans diferentes para produção e para testes de integração anotando métodos
@Beanou classes@Configurationcom@Profile(ou utilizando@TestConfiguration). - Ativamos o perfil desejado nos nossos testes de integração anotando a classe de teste com
@ActiveProfiles.