【问题标题】:How can I validate XML embedded in JSON using Citrus framework?如何使用 Citrus 框架验证嵌入在 JSON 中的 XML?
【发布时间】:2017-10-20 07:10:20
【问题描述】:

在我的 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?

【问题讨论】:

    标签: java json xml citrus-framework


    【解决方案1】:

    使用 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})@")
    

    会做的。

    【讨论】:

    • 很高兴你成功了。我对不需要任何 '\' 或 " 转义的解决方案也非常满意。你对单引号是正确的。隐藏在documentation 中的某个地方显示Please do not forget to mark static text with single quote signs.,即非静态文本不应用单引号标记。这应该在文档中更清楚地说明。
    【解决方案2】:

    澄清

    我对您当前的解决方案有两点意见:

    1. extractFromPayload(..) 将 JSON 中的 body 部分提取到名为 body 的 Citrus 变量中。这与payload(..) 部分没有任何关系。您刚刚将 body 的 JSON 内容存储到 Citrus 变量 body 中。您可能知道也可能不知道这一点

    2. payload(..) 方法将始终验证接收到的整个有效负载,即整个 JSON 对象。

    3. 如果您只想验证部分响应,请使用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('&lt;some&gt;&lt;validation_xml&gt;&lt;/some&gt;&lt;/validation_xml&gt;')@ 放入正确的 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
    • 您当前的示例不是有效的 XML,因为标签 &lt;/soapenv:Body&gt; 从未打开
    • 将有效负载请求放入单独的文件要容易得多,只需注意文件仍然必须是有效的 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"))
    ;
    

    【讨论】:

    • 您好 gucci,感谢您的回复! code .validate("$.requests[0].request.body", @matchesXml('citrus:readFile('classpath:output/esb/add_conf_to_cart/response2.xml')')@) code 看起来有希望。我会试一试,然后告诉你。
    • 请注意,您可以根据需要使用任意数量的validate 检查。因此,您还可以验证响应正文的其他部分。
    • 验证现在比较 XML 部分。我不确定它是否认为数据是 XML。我用过这个:variable("jsonXml", "'citrus:readFile('classpath:output/esb/add_conf_to_cart/response2.xml')'");然后 http() .client(wiremockClient) .receive() .response(HttpStatus.OK) .messageType(MessageType.JSON) .validate("$.requests[0].request.body", "${jsonXml}" ) ;我已经将期望和实际消息复制到文件并使用 IntelliJ 比较。 IntelliJ 认为预期和实际是相等的,但 Citrus 不认为。
    • 你错过了最重要的部分,@matchesXml('')@。它应该是这样的:.validate("$.requests[0].request.body", "@matchesXml('${jsonXml}')")
    • 哦,你是对的,但不应该是 .validate("$.requests[0].request.body", "@matchesXml('${jsonXml}')@" )?尾随@?如果我这样做,我的测试用例会抱怨 xml 无效。 “原因:com.consol.citrus.exceptions.CitrusRuntimeException:org.w3c.dom.ls.LSException:prolog 中不允许内容。”所以它认为它正在处理xml。我的消息来源有点狡猾,所以也许解析器是对的。
    【解决方案3】:

    另一种解决方案使用 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")

    从有效负载中提取的正文内容

    类 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 文档中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-02
      • 2012-09-10
      相关资源
      最近更新 更多