«1. Введение

Spring Cloud Contract — это проект, который, проще говоря, помогает нам писать контракты, управляемые потребителями (CDC).

Это обеспечивает контракт между производителем и потребителем в распределенной системе — как для взаимодействия на основе HTTP, так и на основе сообщений.

В этой быстрой статье мы рассмотрим написание тестовых примеров на стороне производителя и потребителя для Spring Cloud Contract посредством HTTP-взаимодействия.

2. Производитель — серверная сторона

Мы собираемся написать CDC на стороне производителя в форме EvenOddController — который просто сообщает, является ли числовой параметр четным или нечетным:

@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

Для нашей стороны производителя нам понадобится зависимость spring-cloud-starter-contract-verifier:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-contract-verifier</artifactId>
    <version>2.1.1.RELEASE</version>
    <scope>test</scope>
</dependency>

И нам нужно настроить spring-cloud-contract-maven-plugin с помощью имя нашего базового тестового класса, которое мы опишем в следующем разделе:

<plugin>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
    <version>2.1.1.RELEASE</version>
    <extensions>true</extensions>
    <configuration>
        <baseClassForTests>
            com.baeldung.spring.cloud.springcloudcontractproducer.BaseTestClass
        </baseClassForTests>
    </configuration>
</plugin>

2.2. Настройка стороны производителя

Нам нужно добавить базовый класс в тестовый пакет, который загружает наш контекст 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);
    }
}

В пакет /src/test/resources/contracts/ мы добавим тестовые заглушки, такой как этот в файле shouldReturnEvenWhenRequestParamIsEven.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
    }
}

Когда мы запускаем сборку, плагин автоматически генерирует тестовый класс с именем ContractVerifierTest, который расширяет наш BaseTestClass и помещает его в /target/generated-test-sources/contracts /.

Имена тестовых методов получены из префикса «validate_», связанного с именами наших тестовых заглушек Groovy. Для приведенного выше файла Groovy сгенерированное имя метода будет «validate_shouldReturnEvenWhenRequestParamIsEven».

Давайте взглянем на этот автоматически сгенерированный тестовый класс:

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

Сборка также добавит jar-заглушку в наш локальный репозиторий Maven, чтобы ее мог использовать наш потребитель.

Заглушки будут находиться в выходной папке stubs/mapping/.

3. Сторона потребителя — клиентская сторона

Сторона потребителя нашего CDC будет потреблять заглушки, сгенерированные стороной производителя посредством HTTP-взаимодействия, для поддержания контракта, поэтому любые изменения на стороне производителя нарушат контракт.

Добавим BasicMathController, который будет делать HTTP-запрос для получения ответа от сгенерированных заглушек:

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

        return responseEntity.getBody();
    }
}

3.1. Зависимости Maven

Для нашего потребителя нам нужно добавить зависимости spring-cloud-contract-wiremock и spring-cloud-contract-stub-runner:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-wiremock</artifactId>
    <version>2.1.1.RELEASE</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-stub-runner</artifactId>
    <version>2.1.1.RELEASE</version>
    <scope>test</scope>
</dependency>

3.2. Настройка на стороне потребителя

Теперь пришло время настроить наш обработчик заглушек, который будет информировать нашего потребителя о доступных заглушках в нашем локальном репозитории Maven:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc
@AutoConfigureJsonTesters
@AutoConfigureStubRunner(
  stubsMode = StubRunnerProperties.StubsMode.LOCAL,
  ids = "com.baeldung.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"));
    }
}

Обратите внимание, что свойство ids аннотации @AutoConfigureStubRunner указывает: ~~ ~ com.baeldung.spring.cloud — groupId нашего артефакта spring-cloud-contract-producer — ArtiftId производителя заглушек jar 8090 — порт, на котором будут работать сгенерированные заглушки

    4 , Когда контракт нарушен

Если мы внесем какие-либо изменения на стороне производителя, которые напрямую влияют на контракт, не обновляя сторону потребителя, это может привести к сбою контракта.

Например, предположим, что мы должны изменить URI запроса EvenOddController на /validate/change/prime-number на стороне производителя.

Если мы не проинформируем нашего потребителя об этом изменении, потребитель все равно отправит свой запрос на URI /validate/prime-number, а тестовые примеры на стороне потребителя выдадут org.springframework.web.client.HttpClientErrorException: 404 Не обнаружена.

5. Резюме

Мы видели, как Spring Cloud Contract может помочь нам поддерживать контракты между потребителем и производителем сервиса, чтобы мы могли выпускать новый код, не беспокоясь о нарушении контрактов.

И, как всегда, полную реализацию этого руководства можно найти на GitHub.

«