在 pact jvm 合约测试中,当某个 json 字段可能返回数组(含动态结构)或 null 时,无法通过单个 pactdsljsonbody 声明式地表达“数组或空值”语义;正确做法是为两种状态分别编写独立的交互测试用例,以符合 pact 的确定性契约原则。
Pact 的核心设计哲学强调契约的明确性与可验证性:每个交互(interaction)必须定义唯一、确定的请求与响应结构。因此,contents 字段既可能是非空数组(如 [{“param”:”x”,”value”:”y”}]),也可能是 null,这在 Pact 中不被视为“同一字段的可选变体”,而是两种逻辑上不同的响应场景——对应 provider 在不同业务条件下返回的不同有效状态。
Pact JVM 并未提供类似 orNull() 或 eachLike().or().nullValue() 这样的链式语法,其 or() 方法(如 PactDslJsonBody.or())实际用于并列的、互斥的顶层 JSON 类型分支(例如:响应体整体是对象 或 字符串),而非针对某一个字段的类型柔性匹配。试图强行复用同一 PactDslJsonBody 模板覆盖 null 与 eachLike 两种情况,不仅违反 Pact v3 规范对响应体结构确定性的要求,还会导致 Pact Broker 校验失败或消费者/提供者断言歧义。
✅ 正确实践:为每种语义状态定义独立交互
针对你的 120 个 item 场景,推荐采用参数化+模板化策略,避免重复编码,同时严格遵守 Pact 最佳实践:
// 公共基础 body 构建器(不含 contents)
private PactDslJsonBody baseMetadataBody(String itemName, int index) {
return new PactDslJsonBody()
.object("metadata")
.stringValue("name", itemName)
.integerType("index", index);
}
// 场景一:contents 为非空数组
@Test
@PactTestFor(providerName = "item-provider", port = "8080")
public void testItemWithContents(PactDslWithProvider builder) {
String itemName = "item-42";
builder.given("Item " + itemName + " has contents")
.uponReceiving("a request for item info with contents")
.path("/api/item").query("name=" + itemName)
.method("GET")
.willRespondWith()
.status(200)
.body(
baseMetadataBody(itemName, 42)
.eachLike("contents")
.stringType("param", "content-param")
.stringType("value", "content-value")
.closeArray()
.closeObject()
);
}
// 场景二:contents 为 null
@Test
@PactTestFor(providerName = "item-provider", port = "8080")
public void testItemWithoutContents(PactDslWithProvider builder) {
String itemName = "item-99";
builder.given("Item " + itemName + " has no contents")
.uponReceiving("a request for item info without contents")
.path("/api/item").query("name=" + itemName)
.method("GET")
.willRespondWith()
.status(200)
.body(
baseMetadataBody(itemName, 99)
.nullValue("contents") // 显式声明为 null
.closeObject()
);
}
? 关键注意事项: