1. 引言
Spring Cloud Contract 是一个专门用于编写消费者驱动契约(CDC) 的项目。它的核心作用是确保分布式系统中生产者(Producer)和消费者(Consumer)之间的契约一致性,覆盖 HTTP 和消息两种交互场景。
本文将通过一个 HTTP 交互示例,演示如何使用 Spring Cloud Contract 编写生产者和消费者端的测试用例。
2. 生产者端(服务端)
我们创建一个简单的 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 依赖
生产者端需要添加以下依赖:
<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 文件名(如 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. 消费者端(客户端)
消费者端通过 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 依赖
消费者端需要添加以下依赖:
<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. 消费者端配置
配置桩运行器(Stub Runner),使其识别本地 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
:生产者 JAR 的 groupIdspring-cloud-contract-producer
:生产者 JAR 的 artifactId8090
:桩文件运行端口
4. 契约被破坏的场景
如果生产者端修改了影响契约的代码(如接口路径),但未同步更新消费者端,会导致契约测试失败。
🚨 示例踩坑:将生产者端接口路径从 /validate/prime-number
改为 /validate/change/prime-number
,消费者端未更新时:
- 消费者仍请求旧路径
/validate/prime-number
- 抛出异常:
org.springframework.web.client.HttpClientErrorException: 404 Not Found
5. 总结
Spring Cloud Contract 通过以下机制保障服务契约一致性:
- ✅ 自动生成基于契约的测试用例
- ✅ 桩文件隔离生产者与消费者测试
- ✅ 早期发现接口变更导致的契约破坏
完整实现代码可参考 GitHub 仓库。