Spring Cloud Contract 契约测试简介
一、介绍
Spring Cloud Contract是一个项目,简单地说,就是帮助我们编写 消费者驱动的合同(CDC)。
这确保了分布式系统中 Producer和 Consumer之间的契约——用于基于 HTTP 和基于消息的交互。
在这篇快速文章中,我们将探索通过 HTTP 交互为 Spring Cloud Contract 编写生产者和消费者端测试用例。
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");
}
构建还将在我们的本地 Maven 存储库中添加存根 jar,以便我们的使用者可以使用它。
存根将出现在 stubs/mapping/下的输出文件夹中。
3. 消费者 – 客户端
我们CDC的消费者端会通过HTTP交互消费生产者端生成的stub来维护合约,所以生产者端的任何变化都会破坏合约。
我们将添加 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"));
}
}
请注意, @AutoConfigureStubRunner注释的 ids属性指定:
- com.baeldung.spring.cloud —我们的工件的 groupId
- spring-cloud-contract-producer —生产者存根 jar的 artifactId
- 8090 — 生成的存根将在其上运行的端口
4. 合同违约时
如果我们在生产者端进行任何直接影响合约的更改而不更新消费者端,这可能会导致合约失败。
例如,假设我们要将生产者端的 EvenOddController请求 URI 更改为 /validate/change/prime-number。
如果我们没有通知我们的消费者这个变化,消费者仍然会将它的请求发送到 /validate/prime-number URI,消费者端的测试用例将抛出 org.springframework.web.client.HttpClientErrorException: 404 Not Found。
5. 总结
我们已经看到 Spring Cloud Contract 如何帮助我们维护服务消费者和生产者之间的契约,以便我们可以推出新代码而不必担心破坏契约。
而且,一如既往,可以 在 GitHub 上找到本教程的完整实现。