APIs e Microsserviços

22 ago, 2017

Spring cloud contract – Trabalhando com microsserviços

Publicidade

Neste artigo vamos falar sobre Spring Cloud Contract, que é algo novo e que nem todo mundo conhece mas é muito útil para quem está trabalhando com microsserviços. A idéia principal é validar as suas integrações e garantir que o contrato não seja quebrado e que elas funcionem a cada novo deploy.

Vamos iniciar o projeto utilizando o Spring Initializer. Para começar, crie um projeto com as dependências do Cloud Contract Verifier e Web, como na imagem abaixo:

SPRING CLOUD CONTRACT

Agora é preciso adicionar o plugin que gera os stubs para que você possa utilizá-los nos seus testes integrados em outros microsserviços. O código que você tem que inserir está abaixo:

<plugin>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-contract-maven-plugin</artifactId>
 <version>1.1.0.RELEASE</version>
 <extensions>true</extensions>
 <configuration>
 <baseClassForTests>br.com.demo.MockMvcTest</baseClassForTests>
 </configuration>
</plugin>

É possível notar que o plugin possui uma classe base de teste, que é definida dentro da tag configuration do plugin. Essa é a sua classe base onde serão declarados os seus serviços rest que serão testados e terão stubs gerados. Abaixo um exemplo dessa classe:

package br.com.contract.demo;
 
import br.com.contract.demo.api.PaymentRestService;
import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.Before;
 
public class MockMvcTest {
 
 @Before
 public void setup() {
 RestAssuredMockMvc.standaloneSetup(new PaymentRestService());
 }
}

Legal, neste exemplo a classe que está sendo testada é a PaymentRestService. Este é um exemplo que eu criei para este artigo, o que esse serviço faz é: executando um GET na url v1/payments/{id}/status caso o {id} seja maior que dez o retorno será um pagamento não aprovado e, caso seja menor que dez, aí então o retorno será um pagamento aprovado. A classe em questão está no exemplo abaixo:

@RestController
@RequestMapping("v1/payments/{id}/status")
public class PaymentRestService {
 
 @GetMapping(produces = APPLICATION_JSON_VALUE)
 public ResponseEntity&lt;PaymentStatus&gt; getPayment(@PathVariable Integer id) {
 if (id &gt; 10) {
 return ResponseEntity.status(HttpStatus.PRECONDITION_FAILED)
 .body(PaymentStatus.builder().approved(false).build());
 } else {
 return ResponseEntity.ok(PaymentStatus.builder().approved(true).build());
 }
 }
}

Agora que nós já entendemos a classe que será testada, devemos criar um script groovy que será responsável pelo teste, o que ele irá fazer é enviar uma requisição e validar se o retorno está ok. O script para testar pagamento aprovado, deve ser igual ao abaixo:

import org.springframework.cloud.contract.spec.Contract
 
Contract.make {
 request {
 method 'GET'
 url '/v1/payments/1/status'
 headers {
 contentType(applicationJson())
 }
 }
 response {
 status 200
 body("""{"approved":true}""")
 headers {
 contentType(applicationJson())
 }
 }
}

E aqui o script para testar um pagamento não aprovado:

import org.springframework.cloud.contract.spec.Contract
 
Contract.make {
 request {
 method 'GET'
 url '/v1/payments/11/status'
 headers {
 contentType(applicationJson())
 }
 }
 response {
 status 412
 body("""{"approved":false}""")
 headers {
 contentType(applicationJson())
 }
 }
}

Estes scripts devem obrigatoriamente estar na pasta src/test/resources/contracts

Agora quando você executar o comando mvn clean package você verá que na pasta target foram criados alguns arquivos importantes:

  • generated-test-sources/contracts: Nesta pasta está a classe gerada pelo script groovy que, utilizando o RestAssured, valida o seu serviço rest;
  • stubs: Nesta pasta estão seus stubs, que podem ser publicados em um nexus para que outros projetos possam utilizá-los no momento dos testes integrados e, com isso, caso aconteça uma atualização o(s) projeto(s) que o utilizam irão ter seus testes quebrados e você não correrá o risco de subir uma alteração com uma integração não funcionando.

Para testar isso localmente execute o mvn clean install, assim os stubs vão para o seu maven local.

Esta foi a primeira parte, agora vamos criar um serviço que irá chamar a api de payment para validar se um pagamento está aprovado. Para isso, vamos criar um projeto no spring initializer com as seguintes dependências: Cloud Contract Stub Runner e Cloud Contract WireMock, igual ao da imagem abaixo:

SPRING CLOUD CONTRACT

Após fazer o download do projeto, basta criar uma classe igual a classe abaixo para testar o contrato com o primeiro projeto criado. Esta classe irá fazer uma chamada ao stub, que irá retornar o contrato de acordo com os que foram criados com groovy.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
 
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
 
@SpringBootTest
@RunWith(SpringRunner.class)
@AutoConfigureStubRunner(ids = {"br.com.demo:demo:+:stubs:8080"}, workOffline = true)
public class ValidateContractTest {
 
 private RestTemplate restTemplate = new RestTemplate();
 
 @Test
 public void validatePaymentApprovedContract() throws Exception {
 ResponseEntity&lt;String&gt; responseEntity = restTemplate.getForEntity(
 "http://127.0.0.1:8080/v1/payments/1/status", String.class);
 assertThat(responseEntity.getStatusCode(), equalTo(HttpStatus.OK));
 assertThat(responseEntity.getBody(), equalTo( "{\"approved\":true}"));
 }
 
 @Test
 public void validatePaymentNotApprovedContract() {
 try {
 restTemplate.getForEntity("http://127.0.0.1:8080/v1/payments/11/status", String.class);
 } catch (HttpClientErrorException ex) {
 assertThat(ex.getStatusCode(), equalTo(HttpStatus.PRECONDITION_FAILED));
 assertThat(ex.getResponseBodyAsString(), equalTo( "{\"approved\":false}"));
 
 }
 }
}

E é isso, agora todas as suas integrações podem ser mocadas e testadas de forma efetiva e acabou aquele problema de “quem removeu o atributo da api?”. Para ver o código acesse o repo.

Abraços, não deixem de comentar as dúvidas e até o próximo artigo!