Configurando Testes E2E com Spring Boot (Servidor Embarcado e Listeners)
Depois de ler esta aula, você entenderá como podemos configurar nossos testes End-to-End quando estamos escrevendo testes para uma aplicação web que utiliza o Spring Boot.
Durante esta aula, utilizaremos o navegador Google Chrome, mas exatamente as mesmas técnicas funcionam com outros navegadores suportados ou com o Playwright.
Vamos começar descobrindo como podemos iniciar nossa aplicação web quando nossos testes End-to-End forem executados.
1. Iniciando Nossa Aplicação Web nos Testes E2E
Quando estamos escrevendo testes End-to-End para uma aplicação Spring Boot, precisamos iniciar nossa aplicação — junto com seu servidor HTTP embarcado (Tomcat, Jetty ou Netty) — antes que nossos testes sejam executados. Podemos fazer isso anotando nossa classe de teste com @SpringBootTest e configurando seu atributo webEnvironment para WebEnvironment.DEFINED_PORT (ou WebEnvironment.RANDOM_PORT).
Isso garante que o Spring Boot Test carregue o ApplicationContext real da aplicação e inicie o container de servlets embarcado ouvindo na porta HTTP configurada no arquivo application.properties (ou na porta padrão 8080, caso não tenhamos configurado uma porta customizada).
Nota sobre
DEFINED_PORTvsRANDOM_PORT:
- No curso original do Test With Spring, utiliza-se
WebEnvironment.DEFINED_PORT(porta8080) para que os métodos utilitários de URL nos Page Objects possam construir os endereços a partir de uma constante centralizada.- Em pipelines de CI/CD modernos onde múltiplos builds podem rodar na mesma máquina simultaneamente, você também pode utilizar
WebEnvironment.RANDOM_PORTe injetar a porta dinâmica alocada pelo sistema operacional com a anotação@LocalServerPort private var port: Int = 0. Ambos os modos iniciam o servidor HTTP embarcado real!
Depois de anotarmos nossa classe de teste End-to-End com @SpringBootTest, o código-fonte fica assim:
|
|
Vamos descobrir a seguir como podemos gerenciar o ciclo de vida dos nossos objetos WebDriver integrados ao contexto de teste do Spring.
2. Gerenciando o Ciclo de Vida dos Objetos WebDriver
Se não quisermos gerenciar o ciclo de vida dos nossos objetos WebDriver manualmente em cada classe de teste, precisamos encontrar uma maneira de:
- Criar um novo objeto
WebDriverantes que um método de setup (@BeforeEach) seja invocado. Isso garante que possamos utilizar o objetoWebDrivercriado dentro dos nossos métodos@BeforeEach. - Injetar o objeto
WebDrivercriado em nossa classe de teste utilizando a anotação@Autowiredpadrão do Spring. - Fechar o objeto
WebDriver(quit()) depois que todos os métodos de teste de uma classe de teste tiverem sido executados.
Podemos implementar essas funcionalidades criando uma anotação customizada chamada @SeleniumTest e um TestExecutionListener customizado do Spring TestContext Framework que gerencia o ciclo de vida do nosso objeto WebDriver:
Passo 1: Criando a Anotação @SeleniumTest com @TestExecutionListeners
Primeiro, criamos a anotação @SeleniumTest. Utilizamos essa anotação para configurar duas coisas essenciais:
- A classe
TestExecutionListener(SeleniumTestExecutionListener) que será responsável por gerenciar o ciclo de vida do objetoWebDriver. - O tipo da classe
WebDriverque será instanciada antes da execução dos testes.
Ao inspecionarmos o código-fonte da anotação @SeleniumTest, vemos que:
- Ela é anotada com
@TestExecutionListeners. Quando configuramos nossoTestExecutionListenercustomizado, precisamos definir o atributomergeModecomoMergeMode.MERGE_WITH_DEFAULTS. Isso garante que o nossoSeleniumTestExecutionListenerseja adicionado à lista padrão de listeners do Spring Boot (comoDependencyInjectionTestExecutionListenereSqlScriptsTestExecutionListener), em vez de substituí-los! - Ela possui um atributo
driverque retorna o tipo doWebDriver(KClass<out WebDriver>) que será instanciado, com valor padrãoChromeDriver::class.
O código-fonte da anotação SeleniumTest fica assim:
|
|
Passo 2: Implementando o SeleniumTestExecutionListener
Segundo, criamos um TestExecutionListener customizado chamado SeleniumTestExecutionListener, que gerencia o ciclo de vida do nosso objeto WebDriver e o registra no ApplicationContext do Spring para que o DependencyInjectionTestExecutionListener possa injetá-lo via @Autowired na classe de teste!
Podemos escrever essa classe seguindo estes 7 passos:
- Criar a classe
SeleniumTestExecutionListenerestendendoAbstractTestExecutionListenere adicionar uma propriedade privadabrowser: WebDriver?nela. - Sobrescrever o método
getOrder()da interfaceOrderede retornarOrdered.HIGHEST_PRECEDENCE. Isso garante que o nossoSeleniumTestExecutionListenerseja invocado antes doDependencyInjectionTestExecutionListener(que realiza a injeção de dependências@Autowiredna instância de teste). - Sobrescrever o método
prepareTestInstance(testContext: TestContext)da classeAbstractTestExecutionListener. O Spring TestContext Framework invoca esse método antes de preparar a instância da classe de teste. - Encontrar a anotação
@SeleniumTestna classe de teste utilizandoAnnotationUtils.findAnnotation(). - Instanciar um novo objeto
WebDriverutilizandoBeanUtils.instantiateClass()(se obrowserainda não tiver sido criado para essa classe) e armazená-lo no campobrowser. - Obter o
ConfigurableApplicationContexta partir doTestContexte registrar o objetoWebDrivercriado naConfigurableListableBeanFactorysubjacente utilizandoregisterResolvableDependency(WebDriver::class.java, browser). Isso permite que o Spring injete essa instância específica sempre que encontrar@Autowired lateinit var browser: WebDriverna classe de teste! - Sobrescrever o método
afterTestClass(testContext: TestContext)da classeAbstractTestExecutionListener. Dentro desse método, se o campobrowsernão for nulo, fechamos o navegador chamandobrowser.quit()e limpamos a referência.
Vamos analisar o código-fonte completo do SeleniumTestExecutionListener em Kotlin:
|
|
3. Injetando o WebDriver na Classe de Teste com @Autowired
Depois de criarmos a anotação @SeleniumTest e o SeleniumTestExecutionListener, podemos configurar nossa classe de teste End-to-End seguindo estes dois passos simples:
- Anotar a classe de teste com
@SeleniumTest. Como o valor padrão do atributodriveréChromeDriver::class, não precisamos configurar o tipo da classeWebDrivermanualmente. - Adicionar uma propriedade
lateinit var browser: WebDriverna classe de teste e anotá-la com a anotação padrão@Autowireddo Spring.
Depois de seguirmos esses passos, o código-fonte da nossa classe de teste End-to-End fica assim:
|
|
Resumo
Nesta aula, aprendemos que:
- Podemos iniciar nossa aplicação web Spring Boot (com servidor HTTP embarcado real) anotando nossa classe de teste End-to-End com
@SpringBootTeste configurandowebEnvironment = WebEnvironment.DEFINED_PORT(ouRANDOM_PORT). - Podemos gerenciar o ciclo de vida dos nossos objetos
WebDriverimplementando umTestExecutionListenercustomizado (SeleniumTestExecutionListener) comOrdered.HIGHEST_PRECEDENCE. - Ao registrar a instância do
WebDrivercombeanFactory.registerResolvableDependency(WebDriver::class.java, browser), podemos simplesmente injetar o navegador na nossa classe de teste utilizando@Autowired. - Nas próximas duas aulas, veremos como inicializar o schema e popular os dados de teste no banco de dados para nossos cenários End-to-End utilizando o Liquibase e a anotação
@Sql.