如何使用Citrus框架验证嵌入在JSON中的XML?

时间:2017-10-20 07:10:21

标签: java json xml citrus-framework

在我的Citrus测试中,我正在尝试验证嵌入在JSON文档中的XML消息(WireMock的输出)。需要解析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?

3 个答案:

答案 0 :(得分:1)

澄清

我对你目前的解决方案有两点评论:

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

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

  3. 如果您只想验证部分回复,请使用JSON Path validation.validate("$.some.json.path", "someValue")

  4. 解决方案

    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 String中的引号也必须进行转义,因此我们必须编写\\\"
    • Citrus首先将payload字符串解析为JSON,并将其作为JSON对象保留在内部,其中转义的引号未转义。然后它将此String传递给验证函数matchesXml
    • 您当前的示例不是有效的XML,因为永远不会打开标记</soapenv:Body>
    • 将有效负载请求放入单独的文件要容易得多,只要知道该文件仍然必须是有效的JSON,即带有一个反斜杠的引号转义,如\"
    • 您可能希望从单独的文件中读取XML内容。在这种情况下,您可以使用Citrus&#39; 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"))
    ;
    

答案 1 :(得分:0)

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

我犯的错误首先是忘记第二个@然后在参数中添加单引号到matchesXML。所以

.validate("$.requests[0].request.body", "@matchesXml('${jsonXml}')@")

无效,但

.validate("$.requests[0].request.body", "@matchesXml(${jsonXml})@")

将完成这项工作。

答案 2 :(得分:0)

另一种解决方案使用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(&#34; $。requests [0] .request.body&#34;,&#34; body& #34)

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()放置一个名为&#39; body&#39;的变量。在稍后在doExecute()中访问的上下文中。 这个解决方案允许运行任意Java代码,在我的例子中,从SOAP信封中提取XML文档,隐藏在JSON文档中。