Este artigo se concentrará na integração de aplicativos da Web escritos usando o Spring e trabalhando com HTTP. O nome Spring Cloud Contract, na minha opinião, é enganador, pois não tem nada a ver com nuvem.
Será sobre contratos de API.
Para testes de unidade de controladores, mockMCV ou RestAssured são frequentemente usados. Para mokas no lado frontal, são utilizados servidores mosk, como Wiremock ou Pact. Porém, frequentemente, os testes de unidade são escritos por algumas pessoas e outras por moki.
Isso pode levar a problemas com a integração.
Por exemplo, um servidor na ausência de dados pode retornar 204 NO_CONTENT e um cliente pode esperar 200 OK e json vazio.
Não importa qual deles está certo. O problema é que alguém cometeu um erro e ele não será encontrado antes do estágio de integração.
Esse problema foi projetado para ser resolvido por contrato de nuvem de primavera.
O que é o contrato de nuvem de primavera
Este é um arquivo no qual o dialeto yaml ou groovyDSL descreve a aparência da solicitação e resposta. Por padrão, todos os contratos estão na pasta /src/test/resources/contracts/*
.
Por exemplo, teste o ponto de extremidade GET mais simples
@GetMapping("/bets/{userId}") public ResponseEntity<List<Bet>> getBets(@PathVariable("userId") String userId) { List<Bet> bets = service.getByUserId(userId); if (bets.isEmpty()) { return ResponseEntity.noContent().build(); } return ResponseEntity.ok(bets); }
Vamos descrever o contrato
org.springframework.cloud.contract.spec.Contract.make { request { method 'GET' urlPath '/bets/2' } response { status 200 headers { header('Content-Type', 'application/json') } body(''' { "sport":"football", "amount": 1 } ''' ) } }
Além disso, testes de unidade e json's para wiremock são gerados a partir deste arquivo usando o plug-in maven ou gradle.
A descrição JSON do mock para o exemplo acima ficará assim:
{ "id" : "df8f7b73-c242-4664-add3-7214ac6356ff", "request" : { "urlPath" : "/bets/2", "method" : "GET" }, "response" : { "status" : 200, "body" : "{\"amount\":1,\"sport\":\"football\"}", "headers" : { "Content-Type" : "application/json" }, "transformers" : [ "response-template" ] }, "uuid" : "df8f7b73-c242-4664-add3-7214ac6356ff" }
O Wiremock pode ser executado localmente, basta baixar o frasco aqui . Por padrão, json mokee deve colocar a pasta de mappings
.
$java -jar wiremock-standalone-2.18.0.jar
Abaixo, é mostrado o teste gerado. A biblioteca RestAssured é usada por padrão, mas mockMVC ou spockframework podem ser usados.
public class UserControllerTest extends ContractBae { @Test public void validate_get_200() throws Exception {
Deve-se notar que todas as classes geradas herdam alguma classe base (pode haver várias classes base), nas quais todos os parâmetros necessários para os testes são inicializados. O caminho para a classe base é descrito nas configurações do plug-in.
Neste exemplo, a classe base pode ser assim:
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SccDemoApplication.class) public abstract class ContractBae { @LocalServerPort int port; @Autowired protected WebApplicationContext webApplicationContext; @Before public void configureRestAssured() { RestAssured.port = port; MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) .build(); RestAssuredMockMvc.mockMvc(mockMvc); } }
Como resultado, obtemos que os testes e os mokeys foram obtidos de uma fonte. Se os testes de unidade forem aprovados e o front-end condicional for executado em mokas, não haverá problemas com a integração.
Mas isso não é tudo
O Moki pode usar não apenas o front end, mas o próprio aplicativo para integrar-se a outro aplicativo. O Spring pode iniciar um servidor mosk, você só precisa gerar um jar com mokami e passar o caminho para ele @AutoConfigureStubRunner
Anotações @AutoConfigureStubRunner
Digamos que nosso controlador faça HTTP para outro aplicativo:
@GetMapping("/bets/{userId}") public ResponseEntity<List<Bet>> getBets(@PathVariable("userId") String userId) { if(!isUsetExists(userId)) { return ResponseEntity.notFound().build(); } List<Bet> bets = service.getByUserId(userId); if (bets.isEmpty()) { return ResponseEntity.noContent().build(); } return ResponseEntity.ok(bets); } private boolean isUsetExists(String userId) { try { restTemplate.getForObject("/exists/" + userId, Void.class); return true; } catch (HttpStatusCodeException ignore) { return false; } }
Então você só precisa descrever let jar com mokami na classe base
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SccDemoApplication.class) @AutoConfigureStubRunner(ids = {"me.dehasi.contracts.demo:sub-service-stubs:+:stubs:8090"}, stubsMode = StubRunnerProperties.StubsMode.LOCAL) public abstract class ContractBase {
Porque como são testes do controlador, os snippets de ascii-doc podem ser gerados a partir dos mesmos testes (um artigo completo sobre rest-docs já está no hub ).
O que temos
Acontece que temos uma fonte da API do contrato, que é descrita em uma linguagem legível por humanos, e dela geramos testes de unidade (teoricamente também documentação), e a partir dela o moki. Essa abordagem reduz o risco de erros de integração entre aplicativos da web.
Exemplos podem ser vistos no site oficial, por exemplo .
Os exemplos de código no artigo são retirados do projeto mais simples aqui .