如何使用 Citrus 框架验证嵌入 JSON 中的 XML?
How can I validate XML embedded in JSON using Citrus framework?
在我的 Citrus 测试中,我试图验证嵌入在 JSON 文档(WireMock 的输出)中的 XML 消息。 XML 文本需要解析,因为它包含我想忽略的时间戳。 JSON 消息的 XML 部分如下所示:
"requests": [
{
"id": "52844d5a-59de-4288-8000-7f48fcda41e5",
"request": {
"body": "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"> [content omitted] </soapenv:Body></soapenv:Envelope>",
}
]
这是我的Java测试代码:
http()
.client(wiremockClient)
.receive()
.response(HttpStatus.OK)
.messageType(MessageType.JSON)
.extractFromPayload("$.requests[0].request.body", "body")
.payload(new ClassPathResource("output/esb/add_conf_to_cart/response2.xml"))
;
其中 respon2.xml 仅包含 XML 文本(上面显示的 JSON 消息中 "body" 的内容),以及我从 Citrus 得到的错误是
com.consol.citrus.exceptions.CitrusRuntimeException: Failed to parse JSON text
…
Caused by: net.minidev.json.parser.ParseException: Unexpected token <soapenv:Envelope xmlns:soapenv=
那么,如何验证嵌入在 JSON 文档中的 XML?
澄清
我对您当前的解决方案有两点意见:
extractFromPayload(..)
将你 JSON 的 body
部分提取到名为 body
的 Citrus 变量中。这与 payload(..)
部分没有任何关系。您刚刚将 body
的 JSON 内容存储到 Citrus 变量 body
中。您可能知道也可能不知道
payload(..)
方法将始终验证接收到的整个有效载荷,即整个 JSON 对象。
如果您只想验证响应的一部分,请使用JSON Path validation:.validate("$.some.json.path", "someValue")
解决方案
Citrus 为您的用例提供了一个内部方法,请参阅 matchesXml() 的文档。
仅验证 XML 结构
这是最简单的解决方案:
http()
.client(wiremockClient)
.receive()
.response(HttpStatus.OK)
.messageType(MessageType.JSON)
.validate("$.requests[0].request.body", @matchesXml('citrus:readFile('classpath:output/esb/add_conf_to_cart/response2.xml')')@)
正在验证整个 JSON 响应
在您的情况下,您只需匹配整个 JSON 结构并将方法 @matchesXml('<some><validation_xml></some></validation_xml>')@
放入正确的 JSON 条目中:
http()
.client(wiremockClient)
.receive()
.response(HttpStatus.OK)
.messageType(MessageType.JSON)
.payload("{\n"+
" \"requests\": {\n"+
" \"id\": \"52844d5a-59de-4288-8000-7f48fcda41e5\",\n"+
" \"request\": {\n"+
" \"body\": \"@matchesXml('<soapenv:Envelope xmlns:soapenv=\\"http://schemas.xmlsoap.org/soap/envelope/\\"> [content omitted] </soapenv:Body></soapenv:Envelope>')@\"\n"+
" }\n"+
" }\n"+
"}");
备注
- 放入
payload
的字符串必须有效 JSON,即 JSON 值中的引号必须转义。
- Java 字符串中的引号也必须转义,因此我们必须写成
\\"
- Citrus 首先将
payload
字符串解析为 JSON,并在内部将其保存为 JSON 对象,其中转义引号未转义。然后它将此字符串传递给验证函数 matchesXml
.
- 您当前的示例无效 [=112=],因为标签
</soapenv:Body>
从未打开过
- 将负载请求放入单独的文件中要容易得多,请注意该文件仍然必须有效 JSON,即用一个反斜杠转义引号
\"
- 您可能希望从单独的文件中读取 XML 内容。在这种情况下,您可以使用 Citrus 的
citrus:readFile()
函数
- 对于
payload
,您仍然需要在 XML 中转义引号。你可以用 citrus:translate()
来做到这一点:citrus:translate('citrus:readFile('classpath:some/path/to/response.xml')', '\"', '\"')
- 转义非常棘手,因为我们正在处理 Java、JSON 和 XML
中特殊的引号和反斜杠
- 它应该按如下方式工作:
创建文件response_validation.json
:
{
"requests": {
"id": "52844d5a-59de-4288-8000-7f48fcda41e5",
"request": {
"body": "@matchesXml('${jsonEscapedXmlInput}')@"
}
}
}
在您的测试用例中创建 Citrus 变量 jsonEscapedXmlInput
,它读取并转义 XML 文件:
variable("jsonEscapedXml", "citrus:translate('citrus:readFile('classpath:output/esb/add_conf_to_cart/response2.xml')', '\\"', '\\\"')")
那就用吧
http()
.client(wiremockClient)
.receive()
.response(HttpStatus.OK)
.messageType(MessageType.JSON)
.payload(new ClassPathResource("classpath:validation/response_validation.json"))
;
使用 gucce 的解决方案,我终于找到了验证 XML 的正确语法。代码现在看起来像这样:
variable("jsonXml", "'citrus:readFile('classpath:output/esb/add_conf_to_cart/response2.xml')'");
http()
.client(wiremockClient)
.send()
.get("/__admin/requests")
.accept("application/json");
HttpClientResponseActionBuilder body = http()
.client(wiremockClient)
.receive()
.response(HttpStatus.OK)
.messageType(MessageType.JSON)
.validate("$.requests[0].request.body", "@matchesXml(${jsonXml})@")
;
我犯的错误首先是忘记了第二个@,然后是在匹配的参数周围添加了单引号XML。所以
.validate("$.requests[0].request.body", "@matchesXml('${jsonXml}')@")
行不通,但
.validate("$.requests[0].request.body", "@matchesXml(${jsonXml})@")
会完成任务的。
另一种解决方案使用 AbstractTestAction 的实现来调用在 Citrus 上下文中执行验证的方法:
http()
.client(wiremockClient)
.receive()
.response(HttpStatus.OK)
.messageType(MessageType.JSON)
.extractFromPayload("$.requests[0].request.body", "body")
;
action(action(new DummyAction(new File(EXPECTED_OUTPUT_FILE)));
action() 调用注册 DummyAction 以执行验证。在我的例子中,它需要一个文件来验证通过 http().extractFromPayload("$.requests[0].request.body", "body")
Class DummyAction 看起来像这样:
class DummyAction extends AbstractTestAction {
private File expectedOutputFile;
public DummyAction(File expectedOutputFile) {
this.expectedOutputFile = expectedOutputFile;
}
@Override
public void doExecute(TestContext testContext) {
String body = testContext.getVariable("body");
Assert.assertTrue(body.startsWith("<soapenv:Envelope "), "Request should start with '<soapenv:Envelope '");
}
}
doExecute() 可以访问测试上下文中可用的变量。因此 extractFromPayload() 在上下文中放置了一个名为 'body' 的变量,随后在 doExecute() 中访问该变量。
这个解决方案允许任意 Java 代码到 运行,在我的例子中,从 SOAP 信封中提取 XML 文档,埋在 JSON 文档中。
在我的 Citrus 测试中,我试图验证嵌入在 JSON 文档(WireMock 的输出)中的 XML 消息。 XML 文本需要解析,因为它包含我想忽略的时间戳。 JSON 消息的 XML 部分如下所示:
"requests": [
{
"id": "52844d5a-59de-4288-8000-7f48fcda41e5",
"request": {
"body": "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"> [content omitted] </soapenv:Body></soapenv:Envelope>",
}
]
这是我的Java测试代码:
http()
.client(wiremockClient)
.receive()
.response(HttpStatus.OK)
.messageType(MessageType.JSON)
.extractFromPayload("$.requests[0].request.body", "body")
.payload(new ClassPathResource("output/esb/add_conf_to_cart/response2.xml"))
;
其中 respon2.xml 仅包含 XML 文本(上面显示的 JSON 消息中 "body" 的内容),以及我从 Citrus 得到的错误是
com.consol.citrus.exceptions.CitrusRuntimeException: Failed to parse JSON text
…
Caused by: net.minidev.json.parser.ParseException: Unexpected token <soapenv:Envelope xmlns:soapenv=
那么,如何验证嵌入在 JSON 文档中的 XML?
澄清
我对您当前的解决方案有两点意见:
extractFromPayload(..)
将你 JSON 的body
部分提取到名为body
的 Citrus 变量中。这与payload(..)
部分没有任何关系。您刚刚将body
的 JSON 内容存储到 Citrus 变量body
中。您可能知道也可能不知道payload(..)
方法将始终验证接收到的整个有效载荷,即整个 JSON 对象。如果您只想验证响应的一部分,请使用JSON Path validation:
.validate("$.some.json.path", "someValue")
解决方案
Citrus 为您的用例提供了一个内部方法,请参阅 matchesXml() 的文档。
仅验证 XML 结构
这是最简单的解决方案:
http()
.client(wiremockClient)
.receive()
.response(HttpStatus.OK)
.messageType(MessageType.JSON)
.validate("$.requests[0].request.body", @matchesXml('citrus:readFile('classpath:output/esb/add_conf_to_cart/response2.xml')')@)
正在验证整个 JSON 响应
在您的情况下,您只需匹配整个 JSON 结构并将方法 @matchesXml('<some><validation_xml></some></validation_xml>')@
放入正确的 JSON 条目中:
http()
.client(wiremockClient)
.receive()
.response(HttpStatus.OK)
.messageType(MessageType.JSON)
.payload("{\n"+
" \"requests\": {\n"+
" \"id\": \"52844d5a-59de-4288-8000-7f48fcda41e5\",\n"+
" \"request\": {\n"+
" \"body\": \"@matchesXml('<soapenv:Envelope xmlns:soapenv=\\"http://schemas.xmlsoap.org/soap/envelope/\\"> [content omitted] </soapenv:Body></soapenv:Envelope>')@\"\n"+
" }\n"+
" }\n"+
"}");
备注
- 放入
payload
的字符串必须有效 JSON,即 JSON 值中的引号必须转义。 - Java 字符串中的引号也必须转义,因此我们必须写成
\\"
- Citrus 首先将
payload
字符串解析为 JSON,并在内部将其保存为 JSON 对象,其中转义引号未转义。然后它将此字符串传递给验证函数matchesXml
. - 您当前的示例无效 [=112=],因为标签
</soapenv:Body>
从未打开过 - 将负载请求放入单独的文件中要容易得多,请注意该文件仍然必须有效 JSON,即用一个反斜杠转义引号
\"
- 您可能希望从单独的文件中读取 XML 内容。在这种情况下,您可以使用 Citrus 的
citrus:readFile()
函数 - 对于
payload
,您仍然需要在 XML 中转义引号。你可以用citrus:translate()
来做到这一点:citrus:translate('citrus:readFile('classpath:some/path/to/response.xml')', '\"', '\"')
- 转义非常棘手,因为我们正在处理 Java、JSON 和 XML 中特殊的引号和反斜杠
- 它应该按如下方式工作:
创建文件response_validation.json
:
{
"requests": {
"id": "52844d5a-59de-4288-8000-7f48fcda41e5",
"request": {
"body": "@matchesXml('${jsonEscapedXmlInput}')@"
}
}
}
在您的测试用例中创建 Citrus 变量 jsonEscapedXmlInput
,它读取并转义 XML 文件:
variable("jsonEscapedXml", "citrus:translate('citrus:readFile('classpath:output/esb/add_conf_to_cart/response2.xml')', '\\"', '\\\"')")
那就用吧
http()
.client(wiremockClient)
.receive()
.response(HttpStatus.OK)
.messageType(MessageType.JSON)
.payload(new ClassPathResource("classpath:validation/response_validation.json"))
;
使用 gucce 的解决方案,我终于找到了验证 XML 的正确语法。代码现在看起来像这样:
variable("jsonXml", "'citrus:readFile('classpath:output/esb/add_conf_to_cart/response2.xml')'");
http()
.client(wiremockClient)
.send()
.get("/__admin/requests")
.accept("application/json");
HttpClientResponseActionBuilder body = http()
.client(wiremockClient)
.receive()
.response(HttpStatus.OK)
.messageType(MessageType.JSON)
.validate("$.requests[0].request.body", "@matchesXml(${jsonXml})@")
;
我犯的错误首先是忘记了第二个@,然后是在匹配的参数周围添加了单引号XML。所以
.validate("$.requests[0].request.body", "@matchesXml('${jsonXml}')@")
行不通,但
.validate("$.requests[0].request.body", "@matchesXml(${jsonXml})@")
会完成任务的。
另一种解决方案使用 AbstractTestAction 的实现来调用在 Citrus 上下文中执行验证的方法:
http()
.client(wiremockClient)
.receive()
.response(HttpStatus.OK)
.messageType(MessageType.JSON)
.extractFromPayload("$.requests[0].request.body", "body")
;
action(action(new DummyAction(new File(EXPECTED_OUTPUT_FILE)));
action() 调用注册 DummyAction 以执行验证。在我的例子中,它需要一个文件来验证通过 http().extractFromPayload("$.requests[0].request.body", "body")
Class DummyAction 看起来像这样:
class DummyAction extends AbstractTestAction {
private File expectedOutputFile;
public DummyAction(File expectedOutputFile) {
this.expectedOutputFile = expectedOutputFile;
}
@Override
public void doExecute(TestContext testContext) {
String body = testContext.getVariable("body");
Assert.assertTrue(body.startsWith("<soapenv:Envelope "), "Request should start with '<soapenv:Envelope '");
}
}
doExecute() 可以访问测试上下文中可用的变量。因此 extractFromPayload() 在上下文中放置了一个名为 'body' 的变量,随后在 doExecute() 中访问该变量。 这个解决方案允许任意 Java 代码到 运行,在我的例子中,从 SOAP 信封中提取 XML 文档,埋在 JSON 文档中。