1. 概述

本文将介绍 JSONAssert 库——一个专注于理解 JSON 数据并基于此编写复杂 JUnit 测试的实用工具。

2. Maven 依赖

首先添加 Maven 依赖:

<dependency>
    <groupId>org.skyscreamer</groupId>
    <artifactId>jsonassert</artifactId>
    <version>1.5.0</version>
</dependency>

最新版本可在 Maven 中央仓库 查询。

3. 处理简单 JSON 数据

3.1. 使用 LENIENT 模式

先从简单的 JSON 字符串比较开始:

String actual = "{id:123, name:\"John\"}";
JSONAssert.assertEquals(
  "{id:123,name:\"John\"}", actual, JSONCompareMode.LENIENT);

测试会通过,因为预期和实际 JSON 字符串完全一致。

LENIENT 模式的特点是:即使实际 JSON 包含额外字段,测试仍然通过:

String actual = "{id:123, name:\"John\", zip:\"33025\"}";
JSONAssert.assertEquals(
  "{id:123,name:\"John\"}", actual, JSONCompareMode.LENIENT);

实际变量多了 zip 字段,但测试依然通过。✅

这种设计在开发中很实用——API 可以扩展返回新字段,而不会破坏现有测试。

3.2. 使用 STRICT 模式

切换到 STRICT 模式会改变上述行为:

String actual = "{id:123,name:\"John\"}";
JSONAssert.assertNotEquals(
  "{name:\"John\"}", actual, JSONCompareMode.STRICT);

注意这里使用了 assertNotEquals()。❌

3.3. 用布尔值替代 JSONCompareMode

比较模式也可通过布尔值参数指定(false=LENIENT, true=STRICT):

String actual = "{id:123,name:\"John\",zip:\"33025\"}";
JSONAssert.assertEquals(
  "{id:123,name:\"John\"}", actual, JSONCompareMode.LENIENT);
JSONAssert.assertEquals(
  "{id:123,name:\"John\"}", actual, false);

actual = "{id:123,name:\"John\"}";
JSONAssert.assertNotEquals(
  "{name:\"John\"}", actual, JSONCompareMode.STRICT);
JSONAssert.assertNotEquals(
  "{name:\"John\"}", actual, true);

3.4. 逻辑比较

JSONAssert 执行的是逻辑比较,这意味着 JSON 对象的字段顺序不影响结果:

String result = "{id:1,name:\"John\"}";
JSONAssert.assertEquals(
  "{name:\"John\",id:1}", result, JSONCompareMode.STRICT);
JSONAssert.assertEquals(
  "{name:\"John\",id:1}", result, JSONCompareMode.LENIENT);

无论严格还是宽松模式,测试都会通过。✅

另一个逻辑比较的例子是不同类型的相同值:

JSONObject expected = new JSONObject();
JSONObject actual = new JSONObject();
expected.put("id", Integer.valueOf(12345));
actual.put("id", Double.valueOf(12345));

JSONAssert.assertEquals(expected, actual, JSONCompareMode.LENIENT);

这里使用 JSONObject 替代字符串,且预期是 Integer,实际是 Double。由于逻辑值都是 12345,测试通过。✅

嵌套对象比较同样简单粗暴:

String result = "{id:1,name:\"Juergen\", 
  address:{city:\"Hollywood\", state:\"LA\", zip:91601}}";
JSONAssert.assertEquals("{id:1,name:\"Juergen\", 
  address:{city:\"Hollywood\", state:\"LA\", zip:91601}}", result, false);

3.5. 自定义断言消息

所有 assertEquals()assertNotEquals() 方法都支持自定义错误消息:

String actual = "{id:123,name:\"John\"}";
String failureMessage = "Only one field is expected: name";
try {
    JSONAssert.assertEquals(failureMessage, 
      "{name:\"John\"}", actual, JSONCompareMode.STRICT);
} catch (AssertionError ae) {
    assertThat(ae.getMessage()).containsIgnoringCase(failureMessage);
}

测试失败时,错误信息更清晰:

Only one field is expected: name 
Unexpected: id

第一行是自定义消息,第二行是库提供的补充信息。⚠️

4. 处理 JSON 数组

数组比较规则与对象略有不同。

4.1. 数组元素顺序

STRICT 模式下数组元素顺序必须完全一致,但 LENIENT 模式不关心顺序:

String result = "[Alex, Barbera, Charlie, Xavier]";
JSONAssert.assertEquals(
  "[Charlie, Alex, Xavier, Barbera]", result, JSONCompareMode.LENIENT);
JSONAssert.assertEquals(
  "[Alex, Barbera, Charlie, Xavier]", result, JSONCompareMode.STRICT);
JSONAssert.assertNotEquals(
  "[Charlie, Alex, Xavier, Barbera]", result, JSONCompareMode.STRICT);

这在验证 API 返回的排序数组时特别实用。✅

4.2. 数组扩展元素

数组比较不允许扩展元素,即使 LENIENT 模式也不行:

String result = "[1,2,3,4,5]";
JSONAssert.assertEquals(
  "[1,2,3,4,5]", result, JSONCompareMode.LENIENT);
JSONAssert.assertNotEquals(
  "[1,2,3]", result, JSONCompareMode.LENIENT);
JSONAssert.assertNotEquals(
  "[1,2,3,4,5,6]", result, JSONCompareMode.LENIENT);

无论哪种模式,预期数组必须与实际数组完全匹配。增删任何元素都会导致失败。❌

4.3. 数组特定操作

还有两种进阶技巧验证数组内容:

验证数组大小:通过具体语法指定预期大小:

String names = "{names:[Alex, Barbera, Charlie, Xavier]}";
JSONAssert.assertEquals(
  "{names:[4]}", 
  names, 
  new ArraySizeComparator(JSONCompareMode.LENIENT));

"{names:[4]}" 表示预期数组大小为 4。✅

验证数值范围:确保所有元素在指定范围内:

String ratings = "{ratings:[3.2,3.5,4.1,5,1]}";
JSONAssert.assertEquals(
  "{ratings:[1,5]}", 
  ratings, 
  new ArraySizeComparator(JSONCompareMode.LENIENT));

上述示例验证所有评分值在 [1,5] 区间内(含边界值)。超出范围会导致失败。⚠️

5. 高级比较示例

假设 API 返回多个 id(均为整数),需用正则 \d 统一验证:

JSONAssert.assertEquals("{entry:{id:x}}", "{entry:{id:1, id:2}}", 
  new CustomComparator(
  JSONCompareMode.STRICT, 
  new Customization("entry.id", 
  new RegularExpressionValueMatcher<Object>("\\d"))));

JSONAssert.assertNotEquals("{entry:{id:x}}", "{entry:{id:1, id:as}}", 
  new CustomComparator(JSONCompareMode.STRICT, 
  new Customization("entry.id", 
  new RegularExpressionValueMatcher<Object>("\\d"))));

{id:x} 中的 x 是占位符,实际匹配时用 \d 验证。由于 id 位于 entry 内部,需通过 Customization 指定路径。若任何 id 不匹配正则,测试失败。❌

6. 总结

本文通过多个场景展示了 JSONAssert 的实用价值:从简单比较到复杂验证。这个库能显著简化 JSON 测试的编写,尤其在处理动态字段或复杂结构时避免踩坑。

完整示例代码可在 GitHub 获取。


原始标题:Introduction to JSONassert