Ein Intro für einen Spring Cloud-Vertrag

Eine Einführung in den Spring Cloud-Vertrag

1. Einführung

Spring Cloud Contract ist ein Projekt, das uns einfach beim Schreiben vonConsumer-Driven Contracts (CDC) hilft.

Dies stellt den Vertrag zwischen aProducer und aConsumer in einem verteilten System sicher - sowohl für HTTP-basierte als auch für nachrichtenbasierte Interaktionen.

In diesem kurzen Artikel untersuchen wir das Schreiben von Testfällen für Spring Cloud Contract über eine HTTP-Interaktion auf der Seite von Produzenten und Verbrauchern.

2. Produzent - Serverseite

Wir werden eine herstellerseitige CDC in Form einesEvenOddController schreiben - die nur angibt, ob der Parameternumber gerade oder ungerade ist:

@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. Maven-Abhängigkeiten

Für unsere Produzenten benötigen wir die Abhängigkeit vonspring-cloud-starter-contract-verifier:


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

Und wir müssenspring-cloud-contract-maven-plugin mit dem Namen unserer Basistestklasse konfigurieren, den wir im nächsten Abschnitt beschreiben werden:


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

2.2. Herstellerseitiges Setup

Wir müssen dem Testpaket eine Basisklasse hinzufügen, die unseren Spring-Kontext lädt:

@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, wie dieses in der DateishouldReturnEvenWhenRequestParamIsEven.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
    }
}

Wenn wir den Build ausführen,the plugin automatically generates a test class named ContractVerifierTest that extends our BaseTestClass und setzt ihn in/target/generated-test-sources/contracts/.

Die Namen der Testmethoden leiten sich vom Präfix "validate" _ ab, das mit den Namen unserer Groovy-Teststubs verknüpft ist. Für die obige Groovy-Datei lautet der generierte Methodenname“validate_shouldReturnEvenWhenRequestParamIsEven”.

Schauen wir uns diese automatisch generierte Testklasse an:

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");
}

Der Build fügt auch das Stub-Glas in unser lokales Maven-Repository ein, damit es von unserem Verbraucher verwendet werden kann.

Stubs werden im Ausgabeordner unterstubs/mapping/ angezeigt.

3. Verbraucher - Kundenseite

The consumer side of our CDC will consume stubs generated by the producer side durch HTTP-Interaktion, um den Vertrag aufrechtzuerhalten, alsoany changes on the producer side would break the contract.

Wir werdenBasicMathController, hinzufügen, die eine HTTP-Anfrage stellen, um die Antwort von den generierten Stubs zu erhalten:

@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. Die Maven-Abhängigkeiten

Für unseren Verbraucher müssen wir die Abhängigkeitenspring-cloud-contract-wiremock undspring-cloud-contract-stub-runner hinzufügen:


    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. Verbraucherseitiges Setup

Jetzt ist es an der Zeit, unseren Stub-Runner zu konfigurieren, der unseren Verbraucher über die verfügbaren Stubs in unserem lokalen Maven-Repository informiert:

@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"));
    }
}

Beachten Sie, dass die Eigenschaftidsder Annotation@AutoConfigureStubRunnerFolgendes angibt:

  • com.example.spring.cloud - diegroupId unseres Artefakts

  • spring-cloud-contract-producer - dieartifactId des Erzeuger-Stub-Glases

  • 8090 - Der Port, auf dem die generierten Stubs ausgeführt werden

4. Wenn der Vertrag gebrochen ist

Wenn wir auf der Herstellerseite Änderungen vornehmen, die sich direkt auf den Vertrag auswirken, ohne die Verbraucherseite zu aktualisieren,this can result in contract failure.

Angenommen, wir ändern den Anforderungs-URI vonEvenOddControllerauf unserer Herstellerseite in/validate/change/prime-number.

Wenn wir unseren Verbraucher nicht über diese Änderung informieren, sendet der Verbraucher seine Anfrage weiterhin an den URI/validate/prime-number, und die verbraucherseitigen Testfälle werfenorg.springframework.web.client.HttpClientErrorException: 404 Not Found aus.

5. Zusammenfassung

Wir haben gesehen, wie Spring Cloud Contract uns dabei helfen kann, Verträge zwischen einem Service-Konsumenten und einem Produzenten aufrechtzuerhalten, damit wir neuen Code veröffentlichen können, ohne uns Sorgen machen zu müssen, die Verträge zu brechen.

Und wie immer finden Sie die vollständige Implementierung dieses Tutorials inover on GitHub.