Uma introdução ao Spring Cloud Contract

Uma introdução ao Spring Cloud Contract

1. Introdução

Spring Cloud Contract é um projeto que, simplesmente, nos ajuda a escreverConsumer-Driven Contracts (CDC).

Isso garante o contrato entreProducer eConsumer, em um sistema distribuído - para interações baseadas em HTTP e em mensagens.

Neste artigo rápido, vamos explorar a escrita de casos de teste do lado do produtor e do consumidor para o Spring Cloud Contract por meio de uma interação HTTP.

2. Produtor - lado do servidor

Vamos escrever um CDC do lado do produtor, na forma de umEvenOddController - que apenas informa se o parâmetronumber é par ou ímpar:

@RestController
public class EvenOddController {

    @GetMapping("/validate/prime-number")
    public String isNumberPrime(@RequestParam("number") Integer number) {
        return Integer.parseInt(number) % 2 == 0 ? "Even" : "Odd";
    }
}

2.1. Dependências do Maven

Para o nosso lado do produtor, precisaremos da dependênciaspring-cloud-starter-contract-verifier:


    org.springframework.cloud
    spring-cloud-starter-contract-verifier
    2.1.1.RELEASE
    test

E precisaremos configurarspring-cloud-contract-maven-plugin com o nome de nossa classe de teste base, que descreveremos na próxima seção:


    org.springframework.cloud
    spring-cloud-contract-maven-plugin
    2.1.1.RELEASE
    true
    
        
            com.example.spring.cloud.springcloudcontractproducer.BaseTestClass
        
    

2.2. Configuração do lado do produtor

Precisamos adicionar uma classe base no pacote de teste que carrega nosso contexto do Spring:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@DirtiesContext
@AutoConfigureMessageVerifier
public class BaseTestClass {

    @Autowired
    private EvenOddController evenOddController;

    @Before
    public void setup() {
        StandaloneMockMvcBuilder standaloneMockMvcBuilder
          = MockMvcBuilders.standaloneSetup(evenOddController);
        RestAssuredMockMvc.standaloneSetup(standaloneMockMvcBuilder);
    }
}

In the /src/test/resources/contracts/ package, we’ll add the test stubs, como este no arquivoshouldReturnEvenWhenRequestParamIsEven.groovy:

import org.springframework.cloud.contract.spec.Contract
Contract.make {
    description "should return even when number input is even"
    request{
        method GET()
        url("/validate/prime-number") {
            queryParameters {
                parameter("number", "2")
            }
        }
    }
    response {
        body("Even")
        status 200
    }
}

Quando executamos a compilação,the plugin automatically generates a test class named ContractVerifierTest that extends our BaseTestClasse o coloca em/target/generated-test-sources/contracts/.

Os nomes dos métodos de teste são derivados do prefixo “validate” _ concatenado com os nomes de nossos stubs de teste do Groovy. Para o arquivo Groovy acima, o nome do método gerado será“validate_shouldReturnEvenWhenRequestParamIsEven”.

Vamos dar uma olhada nesta classe de teste gerada automaticamente:

public class ContractVerifierTest extends BaseTestClass {

@Test
public void validate_shouldReturnEvenWhenRequestParamIsEven() throws Exception {
    // given:
    MockMvcRequestSpecification request = given();

    // when:
    ResponseOptions response = given().spec(request)
      .queryParam("number","2")
      .get("/validate/prime-number");

    // then:
    assertThat(response.statusCode()).isEqualTo(200);

    // and:
    String responseBody = response.getBody().asString();
    assertThat(responseBody).isEqualTo("Even");
}

A construção também adicionará o stub jar em nosso repositório Maven local para que possa ser usado por nosso consumidor.

Os stubs estarão presentes na pasta de saída emstubs/mapping/.

3. Consumidor - Lado do Cliente

The consumer side of our CDC will consume stubs generated by the producer side por meio de interação HTTP para manter o contrato, entãoany changes on the producer side would break the contract.

AdicionaremosBasicMathController,, que fará uma solicitação HTTP para obter a resposta dos stubs gerados:

@RestController
public class BasicMathController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/calculate")
    public String checkOddAndEven(@RequestParam("number") Integer number) {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("Content-Type", "application/json");

        ResponseEntity responseEntity = restTemplate.exchange(
          "http://localhost:8090/validate/prime-number?number=" + number,
          HttpMethod.GET,
          new HttpEntity<>(httpHeaders),
          String.class);

        return responseEntity.getBody();
    }
}

3.1. As dependências do Maven

Para nosso consumidor, precisaremos adicionar as dependênciasspring-cloud-contract-wiremock espring-cloud-contract-stub-runner:


    org.springframework.cloud
    spring-cloud-contract-wiremock
    2.1.1.RELEASE
    test


    org.springframework.cloud
    spring-cloud-contract-stub-runner
    2.1.1.RELEASE
    test

3.2. Configuração do lado do consumidor

Agora é hora de configurar nosso executor de stub, que informará nosso consumidor sobre os stubs disponíveis em nosso repositório Maven local:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc
@AutoConfigureJsonTesters
@AutoConfigureStubRunner(
  stubsMode = StubRunnerProperties.StubsMode.LOCAL,
  ids = "com.example.spring.cloud:spring-cloud-contract-producer:+:stubs:8090")
public class BasicMathControllerIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void given_WhenPassEvenNumberInQueryParam_ThenReturnEven()
      throws Exception {

        mockMvc.perform(MockMvcRequestBuilders.get("/calculate?number=2")
          .contentType(MediaType.APPLICATION_JSON))
          .andExpect(status().isOk())
          .andExpect(content().string("Even"));
    }
}

Observe que a propriedadeids da anotação@AutoConfigureStubRunner especifica:

  • com.example.spring.cloud - ogroupId do nosso artefato

  • spring-cloud-contract-producer - oartifactId do jar stub do produtor

  • 8090 - a porta na qual os stubs gerados serão executados

4. Quando o contrato é quebrado

Se fizermos quaisquer alterações no lado do produtor que afetem diretamente o contrato sem atualizar o lado do consumidor,this can result in contract failure.

Por exemplo, suponha que devemos alterar o URI de solicitaçãoEvenOddController para/validate/change/prime-number em nosso lado do produtor.

Se não informarmos nosso consumidor sobre essa mudança, o consumidor ainda enviará sua solicitação para o URI/validate/prime-number, e os casos de teste do lado do consumidor lançarãoorg.springframework.web.client.HttpClientErrorException: 404 Not Found.

5. Sumário

Vimos como o Spring Cloud Contract pode nos ajudar a manter contratos entre um consumidor e produtor de serviço para que possamos lançar um novo código sem qualquer preocupação em quebrar os contratos.

E, como sempre, a implementação completa deste tutorial pode ser encontradaover on GitHub.