Testes de Contrato (CDC), Parte 2: Consumidor e Stub Runner
Ao finalizar esta aula (baseada no webinar bônus), seremos capazes de:
- Criar um projeto Gradle (Kotlin DSL) ou Maven para um serviço consumidor REST.
- Implementar um serviço consumidor que consome a API fornecida pelo serviço provedor (produtor).
- Compreender como podemos garantir que o contrato entre o nosso consumidor e o produtor não sofreu alterações.
- Escrever testes automatizados para o nosso serviço consumidor utilizando o Spring Cloud Contract Stub Runner.
Esta aula assume que:
Vamos começar criando o projeto para o nosso serviço consumidor REST.
Criando um Projeto para um Serviço Consumidor REST
Como a nossa aplicação de exemplo utiliza o Spring Boot 4.1.2 com Kotlin 2.4.21, a estrutura do nosso projeto no Gradle 9.8.2 (Kotlin DSL) (ou Maven) é essencialmente a de um projeto Spring Boot convencional. Contudo, há dois pontos fundamentais que precisamos destacar na configuração do build:
Primeiro, precisamos adicionar o BOM (Bill of Materials) do Spring Cloud Contract (spring-cloud-contract-dependencies) ao gerenciamento de dependências do nosso build. Esse BOM especifica e alinha as versões de todas as bibliotecas do ecossistema Spring Cloud Contract (versão 5.0.0 na stack moderna com Spring Boot 4.1.2; ou 2.0.1.RELEASE no projeto Maven legado original).
No Gradle 9.8.2 (build.gradle.kts), declaramos a importação do BOM dentro do bloco dependencyManagement (ou via platform no bloco dependencies):
|
|
Se estivermos utilizando um arquivo pom.xml no Maven, adicionamos o seguinte trecho:
|
|
Segundo, como queremos substituir o serviço produtor real por um stub durante a execução dos testes, precisamos declarar a dependência spring-cloud-starter-contract-stub-runner no escopo de teste. Esse componente é capaz de baixar e executar automaticamente os stubs WireMock gerados pelo projeto do nosso serviço produtor REST.
Podemos declarar essa dependência adicionando o seguinte trecho à seção dependencies do nosso arquivo build.gradle.kts:
|
|
Ou, no caso de um arquivo pom.xml do Maven:
|
|
Agora criamos um projeto configurado para compilar e executar o nosso serviço consumidor REST. A seguir, implementaremos o código do nosso serviço consumidor REST.
Implementando um Serviço Consumidor REST
Como nos lembramos da Parte 1, o nosso serviço produtor REST retorna as informações de uma única tarefa (Task). Agora, escreveremos um serviço consumidor REST que invoca o serviço produtor REST e retorna as informações devolvidas por ele. Podemos implementar o nosso serviço consumidor seguindo estes passos:
Primeiro, precisamos criar uma classe que armazene os dados de uma tarefa. Em Kotlin 2.4, utilizamos uma data class imutável. Após escrevermos a classe Task, o seu código-fonte fica da seguinte forma:
|
|
(Nota: Na versão original em Java, Task era uma classe com campos private final Long id e private final String title, construtor parametrizado e métodos getters).
Segundo, precisamos escrever o serviço consumidor REST propriamente dito seguindo estes passos:
- Criar uma nova classe de controller (
@RestController) e garantir que seus métodos vinculem o valor retornado diretamente ao corpo da resposta HTTP retornada. - Adicionar um campo
RestTemplate(ouRestClientno Spring Boot 4) à nossa classe de controller e escrever um construtor que injete e construa a instância deRestTemplatea partir doRestTemplateBuilder. - Escrever um método de controller que processe requisições HTTP
GETenviadas para o caminho:/api/task/{id}. - Buscar a tarefa solicitada a partir do nosso serviço produtor REST (
http://localhost:8080/api/task/{id}) e retornar a tarefa encontrada.
Após escrevermos a nossa classe de controller em Kotlin, o seu código-fonte fica da seguinte forma:
|
|
Observação sobre as portas dos serviços:
Antes de continuarmos, vale destacar que os nossos serviços produtor e consumidor escutam em portas diferentes para evitar conflitos quando executados localmente:
- O nosso serviço produtor REST escuta na porta
8080.- O nosso serviço consumidor REST escuta na porta
9000.
Já implementamos o nosso serviço consumidor REST. Vamos avançar e descobrir como podemos escrever testes automatizados para o nosso serviço consumidor.
Escrevendo Testes para o Nosso Serviço Consumidor REST
Quando escrevemos testes para um serviço consumidor REST utilizando contratos dirigidos pelo consumidor (Consumer-Driven Contracts), podemos atingir dois objetivos complementares: garantir que o contrato fornecido pelo serviço produtor REST não mudou ou verificar se o próprio serviço consumidor REST está funcionando conforme o esperado de ponta a ponta.
Pré-requisito importante antes de executar os testes:
Antes de podermos executar esses testes no projeto consumidor, precisamos gerar os stubs WireMock que “implementam” os nossos contratos dirigidos pelo consumidor e publicar esses artefatos de stub no nosso repositório Maven local (
~/.m2/repository).Podemos fazer isso executando o comando
./gradlew clean publishToMavenLocal(em projetos Gradle configurados com o pluginmaven-publish) oumvn clean installno diretório raiz do projeto que compila e empacota o nosso serviço produtor REST.
A seguir, descobriremos como podemos garantir que o contrato fornecido pelo nosso serviço produtor REST não sofreu alterações.
Escrevendo Testes para o Nosso Contrato
Se quisermos garantir diretamente que o contrato fornecido pelo nosso serviço produtor REST não mudou, devemos seguir estes passos:
Primeiro, precisamos criar uma nova classe de teste e configurar o sistema sob teste (System Under Test) seguindo estas etapas:
- Garantir que os nossos testes sejam executados com o suporte do Spring TestContext Framework no JUnit Jupiter 6.0.3 (integrado automaticamente pela anotação
@SpringBootTest, que no JUnit 4 exigia@RunWith(SpringRunner.class)). - Habilitar as melhorias de teste fornecidas pelo Spring Boot (
@SpringBootTest). - Garantir que os nossos métodos de teste utilizem os stubs WireMock gerados pelo projeto do nosso serviço produtor REST por meio da anotação
@AutoConfigureStubRunner. Ao fazer isso, devemos configurarstubsMode = StubRunnerProperties.StubsMode.LOCALpara garantir que os stubs sejam buscados do repositório Maven local, além de especificar nas coordenadas (ids) que esses stubs devem escutar na porta8080.
Após configurarmos o sistema sob teste, o código-fonte da nossa classe de teste fica da seguinte forma:
|
|
Segundo, precisamos adicionar um campo RestTemplate à nossa classe de teste e implementar um método de configuração (@BeforeEach no JUnit Jupiter 6, equivalente ao @Before do JUnit 4) que crie uma nova instância de RestTemplate e a armazene no campo restTemplate.
Após criarmos o RestTemplate que será utilizado pelos nossos métodos de teste, o código-fonte da nossa classe de teste fica assim:
|
|
Terceiro, precisamos garantir que o nosso contrato retorne a resposta HTTP correta quando enviarmos uma requisição GET para a URL que aciona o contrato (http://localhost:8080/api/task/1). Podemos fazer isso seguindo estes passos:
- Garantir que o nosso contrato retorne o código de status HTTP
200(HttpStatus.OK). - Verificar se o nosso contrato retorna uma tarefa que possui o
idcorreto (1L). - Garantir que o nosso contrato retorne uma tarefa que possui o
titlecorreto ("Write our first contract").
Após escrevermos os métodos de teste necessários, o código-fonte completo da nossa classe de teste fica da seguinte forma:
|
|
Quando executamos os nossos testes, vemos que todos passam! Agora conseguimos verificar se o contrato fornecido por um produtor REST não mudou. No entanto, esse teste isolado avalia apenas a comunicação direta com o stub e não garante que o controller do nosso serviço consumidor REST esteja funcionando conforme o esperado. Vamos avançar e descobrir como podemos escrever testes para o nosso serviço consumidor REST.
Escrevendo Testes para o Nosso Serviço Consumidor REST
Quando queremos garantir que o nosso serviço consumidor REST (TaskController) está funcionando conforme o esperado, devemos seguir estes passos:
Primeiro, precisamos criar uma nova classe de teste e configurar o sistema sob teste seguindo estas etapas:
- Garantir que os nossos testes sejam executados com o suporte do Spring no JUnit Jupiter 6 (via
@SpringBootTest, substituindo o@RunWith(SpringRunner.class)do JUnit 4). - Habilitar as melhorias de teste fornecidas pelo Spring Boot (
@SpringBootTest). - Habilitar a autoconfiguração do framework Spring MVC Test (
@AutoConfigureMockMvc). - Garantir que os nossos métodos de teste utilizem os stubs WireMock gerados pelo projeto do nosso serviço produtor REST (
@AutoConfigureStubRunner). Ao fazer isso, precisamos garantir que esses stubs sejam buscados do repositório Maven local (StubsMode.LOCAL) e que escutem na porta8080.
Após configurarmos o sistema sob teste, o código-fonte da nossa classe de teste fica da seguinte forma:
|
|
Segundo, precisamos adicionar um campo MockMvc à nossa classe de teste e garantir que o container do Spring injete a instância real de MockMvc nesse campo utilizando @Autowired.
Após adicionarmos esse campo à nossa classe de teste, o seu código-fonte fica da seguinte forma:
|
|
Terceiro, precisamos garantir que o nosso serviço consumidor REST (integrado ao stub do serviço produtor REST) funcione conforme o esperado quando a tarefa solicitada for encontrada. Podemos escrever esses testes seguindo estes passos:
- Garantir que o serviço retorne o código de status HTTP
200(Ok). - Verificar se o serviço retorna as informações da tarefa encontrada no formato JSON (
MediaType.APPLICATION_JSON, que substituiu a constante depreciadaMediaType.APPLICATION_JSON_UTF8das versões antigas do Spring). - Garantir que o serviço retorne o payload JSON com as informações corretas (
idigual a1etitleigual a"Write our first contract").
Após escrevermos esses testes, o código-fonte completo da nossa classe de teste fica da seguinte forma:
|
|
Quando executamos os nossos testes, vemos que todos passam! Agora sabemos como implementar um serviço consumidor REST e garantir que ele esteja funcionando exatamente conforme o esperado usando stubs gerados a partir de contratos. Vamos resumir o que aprendemos nesta aula.
Resumo
Esta aula nos ensinou quatro conceitos fundamentais:
- O Spring Cloud Contract Stub Runner é capaz de baixar e executar automaticamente os stubs WireMock gerados pelo projeto de um serviço produtor REST.
- Antes de podermos executar esses testes no consumidor, precisamos gerar os stubs WireMock que “implementam” os nossos contratos dirigidos pelo consumidor e instalar/publicar esses stubs no repositório Maven local para que possam ser utilizados pelos nossos testes de integração.
- Podemos configurar os stubs inicializados utilizando a anotação
@AutoConfigureStubRunner. - Ao escrevermos testes com o Stub Runner, podemos tanto garantir que o contrato fornecido pelo nosso serviço produtor REST não sofreu alterações quanto verificar se o nosso próprio serviço consumidor REST está funcionando conforme o esperado.