跳转到主内容
极星编程网:以代码为星,赴技术山海!

Pact JVM:为可选数组字段(null 或非空)编写弹性响应契约测试

在 pact jvm 中,当 api 响应中某个字段可能为 `null` 或一个非空数组(如 `contents: null` 或 `contents: [...]`)时,无法通过单个 `pactdsl js onbody` 表达“或”逻辑;正确做法是为两种值态分别定义独立的交互契约测试用例。 Pact 的核心设计哲学强调 契约的确定性与可验证性 ——它不支持运行时模糊匹配(如 or(null, eachLike(...))),因为这会削弱契约对提供方行为的精确约束力。尽管 PactDslJsonBody.or() 方法存在,但它仅用于 并列的、结构完全一致的可选值类型 (例如 stringType("status") 或 integerType("status")), 不适用于混合类型场景(如 null vs. array of objects) ,且 Pact V3 规范本身禁止在单个交互中对同一路径声明歧义性匹配。 因此,针对 contents 字段可能为 null 或非空数组的场景,必须采用 双契约模式 :为同一请求路径(如 GET /item?name=item-1)注册两个独立的 Interaction,分别描述两种合法响应形态: SEO GPT 免费的白帽SEO,PPC和网站经销商平台 下载
// Case 1: contents is null builder .given("Item 'item-1' has no contents") .uponReceiving("a request for item-1 with null contents") .path("/item") .query("name=item-1") .method("GET") .willRespondWith(200) .body(new PactDslJsonBody() .object("metadata") .stringValue("name", "item-1") .integerType("index", 1) .nullValue("contents") // ✅ 显式声明为 null .closeObject() );
// Case 2: contents is a non-empty array builder .given("Item 'item-1' has contents") .uponReceiving("a request for item-1 with populated contents") .path("/item") .query("name=item-1") .method("GET") .willRespondWith(200) .body(new PactDslJsonBody() .object("metadata") .stringValue("name", "item-1") .integerType("index", 1) .eachLike("contents") // ✅ 使用 eachLike 描述数组元素结构 .stringType("param") .stringType("value") .closeArray() .closeObject() );
⚠️ 关键注意事项:两个交互必须具有唯一且语义清晰的 uponReceiving 描述,便于提供方理解上下文;given 状态(如 "Item 'item-1' has no contents")需在提供方测试中被真实模拟(例如通过 WireMock stub 或数据库预置),确保其能返回对应形态;对于 120 个 item,可通过参数化循环生成这两组交互(推荐使用 JUnit 5 @ParameterizedTest + @MethodSource),避免硬编码冗余;切勿尝试用 regexType() 或 booleanType() 替代 nullValue() —— Pact 不允许对 null 进行类型通配,nullValue("contents") 是唯一合规方式。 这种模式看似增加测试数量,实则提升了契约的 可读性、可调试性与提供方实现的明确性 。Pact 报告将清晰指出:“当状态为 X 时,期望 contents 为 null;当状态为 Y 时,期望 contents 为符合 schema 的数组”,从而真正驱动前后端就边界条件达成共识。

相关文章