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.