I am testing a controller using MockMvc. This is what the response looks like:
MockHttpServletResponse:
Status = 200
Error message = null
Headers = {Content-Type=[text/xml]}
Content type = text/xml
Body = <?xml version="1.0" encoding="UTF-8" standalone="yes"?><ns2:diagnosisCode xmlns:ns2="http://schemas.mycompany.co.za/health" effectiveStartDate="2014-03-05T00:00:00+02:00" effectiveEndDate="2014-03-05T23:59:59.999+02:00" diagnosisId="1"><diagnosisCodeId><codingSchemaCode>irrelevant schema</codingSchemaCode><diagnosisCode>irrelevant code</diagnosisCode></diagnosisCodeId></ns2:diagnosisCode>
Forwarded URL = null
Redirected URL = null
Cookies = []
Pretty-printed version of the Body line:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:diagnosisCode xmlns:ns2="http://schemas.mycompany.co.za/health" effectiveStartDate="2014-03-05T00:00:00+02:00" effectiveEndDate="2014-03-05T23:59:59.999+02:00" diagnosisId="1">
<diagnosisCodeId>
<codingSchemaCode>irrelevant schema</codingSchemaCode>
<diagnosisCode>irrelevant code</diagnosisCode>
</diagnosisCodeId>
</ns2:diagnosisCode>
The call on MockMvc looks like
mockMvc.perform(
get("/diagnostic/diagnosisCodes/{schema}/{code}", IRRELEVANT_SCHEMA, IRRELEVANT_CODE).accept(MediaType.TEXT_XML))
.andDo(print())
.andExpect(content().contentType(MediaType.TEXT_XML))
.andExpect(status().isOk())
.andExpect(xpath("diagnosisCodeId/diagnosisCode").string(IRRELEVANT_CODE))
.andExpect(xpath("diagnosisCodeId/codingSchemaCode").string(IRRELEVANT_SCHEMA));
I am pretty sure I am misunderstanding how I'm supposed to use XPath here, but why is this assertion failing? What should my expectation look like?
java.lang.AssertionError: XPath diagnosisCode expected:<irrelevant code> but was:<>
I'm not totally sure what the XPath context is (or whether it is the document node), but I see two possible problems and guess both apply:
You try to match < diagnosisCodeId/> elements that are the root element. There are none, but they're children of <diagnosisCode>. Either include an axis step for the root node (probably better way) or use the descendant-or-self axis step // in front of the query.
/diagnosisCode/diagnosisCodeId/diagnosisCode
//diagnosisCodeId/diagnosisCode
The document uses namespaces (for the root element). In addition to the root element problem described above, either register that namespace (better solution, but I don't know how to do this in spring MVC) or ignore it using following workaround:
/*[local-name() = 'diagnosisCode']/diagnosisCodeId/diagnosisCode
Which first matches all child nodes, but then limits to the ones having the apropriate element name (ignoring namespaces).
By adding XPath 2.0 support (for example by including Saxon as library), you can also use the wildcard namespace matcher:
/*:diagnosisCode/diagnosisCodeId/diagnosisCode
If you register the namespace URI http://schemas.mycompany.co.za/health as ns2, the query would look like
/ns2:diagnosisCode/diagnosisCodeId/diagnosisCode
There is an overload for xpath that takes a Map<String, String> of namespaces:
Map<String, String> ns = Map.of("ns2", "http://schemas.mycompany.co.za/health");
mockMvc.perform(get("/diagnostic/diagnosisCodes/{schema}/{code}", IRRELEVANT_SCHEMA, IRRELEVANT_CODE)
.accept(MediaType.TEXT_XML))
.andExpect(xpath("ns2:diagnosisCodeId/diagnosisCode", ns).string(IRRELEVANT_CODE))
.andExpect(xpath("ns2:diagnosisCodeId/codingSchemaCode", ns).string(IRRELEVANT_SCHEMA));
Related
<?xml version="1.0" encoding="UTF-8"?>
<IDMResponse xmlns="http://www.nrf-arts.org/IXRetail/namespace/" MajorVersion="1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ARTSHeader>
<MessageID>1</MessageID>
<Response ResponseCode="Ok">
<RequestID>1</RequestID>
</Response>
</ARTSHeader>
</IDMResponse>
I tried
//[local-name() = 'Response']
and
#//*[local-name() = 'Response']
In stead of
//[local-name() = 'Response']
use
//*[local-name() = 'Response']
The predicate ([]) needs something to filter on.
// selects nothing. It tells to look for any descendant something. But it needs at least a following attribute or node to know what to do.
/* selects the root-node independend of name of element
//* selects all element-nodes independend of name
//# selects all attributes independend of name
//Response selects all elements with the name Response that are not in a namespace
//*[local-name()='Response'] selects all elements with the name Response independend off a namespace
//*[local-name()='Response' and #ResponseCode='Ok'] selects all elements with the name Response(independend off a namespace) and that have a attribute ResponseCode with the value Ok
Here are some more examples.
And take a look at this answer
To access the <Response ResponseCode="Ok"> node you can use
//Response
To extract the Ok value from there this should work:
//Response/#ResponseCode
Preamble
I have a JMeter script with an XPath Extractor, in this I have specified a query that gets multiple values from the XML document. This all works fine
XML
<?xml version="1.0" encoding="UTF-8"?>
<InventoryAvailabilityAdvice>
<Warehouse>WFC2</Warehouse>
<Timestamp>2019-07-31T23:00:02.177</Timestamp>
<InventoryItem>
<ItemNumber>80903</ItemNumber>
<AvailableQuantity UnitOfMeasure="EA">13</AvailableQuantity>
</InventoryItem>
<InventoryItem>
<ItemNumber>80901</ItemNumber>
<AvailableQuantity UnitOfMeasure="EA">17</AvailableQuantity>
</InventoryItem>
</InventoryAvailabilityAdvice>
Problem
When I then try to get these values in a loop using a JSR232 Sampler they don't seem to come out in the order declared in the XPath Query.
I expected theData_2 to contain the UnitOfMeasure attribute and theData_3 to contain the quantity, but as you can see they are reversed.
Question
Is this expected behavior? If so, when an element has multiple attributes how do I know which order those will be made available as?
Thanks
The order of XPath nodesets produced by union operator is not guaranteed, you can see putValuesForXPathInList() function for implementation details
Actually if you've decided to go for Groovy - you don't even need the XPath Extractor, you can use XmlSlurper class for parsing the XML response.
Example code:
def response = new XmlSlurper().parseText(prev.getResponseDataAsString())
response.InventoryItem.eachWithIndex { item, index ->
log.info('Item: ' + index)
log.info('ItemNumber: ' + item.ItemNumber)
log.info('AvailableQuantiry: ' + item.AvailableQuantity)
log.info('UnitOfMeasure:' + item.AvailableQuantity.#UnitOfMeasure)
}
Demo:
References:
Groovy: Processing XML
Apache Groovy - Why and How You Should Use It
I have xml like below, in the runtime i want to dynamically process . First time first then etc
<tag1>
<tag2>
.
.
.
</tag2>
<tag2>
.
.
.
</tag2>
</tag1>
CASE1: Working code sending static xslt parameter:
.transform(Transformers.xslt(this.config.getSome().getXslt(), xsltParameters(1 or 2)))
public Tuple2[] xsltParameters(int number)
{
final SpelExpressionParser parser = new SpelExpressionParser();
final List<Tuple2<String,Expression>> parameters = new ArrayList<>();
parameters.add(Tuples.of("Id1", parser.parseRaw(String.format("headers['%s']",ID1))));
parameters.add(Tuples.of("Id2", parser.parseRaw(String.format("headers['%s']",ID2))));
parameters.add(Tuples.of("tagNumber", parser.parseRaw(String.format("%d",number))));
return parameters.toArray(new Tuple2[]{});
}
Above is working and transformer can trasnform xslt.
CASE2: Getting error when i want to pass dynamic number:
.transform(Message.class, m-> Transformers.xslt(this.config.getSome().getXslt(), xsltParameters())
xsltparametr(){
parameters.add(Tuples.of("tagNumber",
parser.parseRaw(String.format("headers['%s']",COUNT))));
}
ERROR while transforming:
Error on line 158 of test.xslt(Prior to line 158 is working fine)
XPTY0004: A sequence of more than one item is not allowed as the first argument of starts-with() ("xxmmyty", "xxmmyty")
My xslt at line 158
<xsl:if test="starts-with(xc:tag1/xc:tag2[$count]/xc:trade/xcml:header/xcml:Identifier/xcml:tradeId[#IdScheme='urn:abcd.something'], 'XYZ')" >
<xsl:value-of select="tag" />
</xsl:if>
In CASE1 with same xslt file i can pass above code but in case of dynamic passing(CASE2) it is failing.
First of all it looks like you use old enough Spring version.
Would be grate to upgrade at least to 5.0.x.
Secondly it isn't clear what is your xsltParameters(). You hide it from us and we fully don't know what you do over there. Although we can assume that you are fully based on this method:
#SafeVarargs
public static XsltPayloadTransformer xslt(Resource xsltTemplate,
Tuple2<String, Expression>... xslParameterMappings) {
So, you build in that xsltParameters() an array of Tuple2. OK. So, get access to the header in the param you need to build an appropriate expression.
I'm rally sure that it can be a FunctionExpression:
.transform(Transformers.xslt(this.config.getSome().getXslt(),
Tuples.of("foo", new FunctionExpression<Message<?>>(m -> m.getHeader.get("int_val"))))).
What you do with the lambda can't be combined with a transformer from that factory.
UPDATE
You need to modify your xsltParameters() to accept a String instead. Which is going to be just raw expression representation.
So, your CASE1 is going to be like this:
.transform(Transformers.xslt(this.config.getSome().getXslt(), xsltParameters("'1'")))
With meaning as literal expression.
The CASE2 is going to be like this:
.transform(Transformers.xslt(this.config.getSome().getXslt(), xsltParameters("headers.int_val")))
We have some intergation tests over #RestController with a common pattern to verify that an Xpath expression exists and that an Http header is set. But I would like to go further and verify that the XPath value is equald or contained into the header.
mvc.perform(..)
.andExpect(xpath("Item/#id/").isIn(header("Location")))
Is it something for that or should I create my own ResultMatcher ?
org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath(xpathExpress, args) is what you want.
For example:
ResultActions resultActions = mvc.perform(..);
String location = resultActions.andReturn().getResponse().getHeader("Location");
resultActions.andExpect(MockMvcResultMatchers.xpath("Item/#id/", null)
.string(org.hamcrest.Matchers.containsString(location)));
If you need to compare by Node, XMLUnit for Java 2.x offers more usefule Matcher.
I could not find much examples of evaluate XPath using xerces-c 3.1.
Given the following sample XML input:
<abc>
<def>AAA BBB CCC</def>
</abc>
I need to retrieve the "AAA BBB CCC" string by the XPath "/abc/def/text()[0]".
The following code works:
XMLPlatformUtils::Initialize();
// create the DOM parser
XercesDOMParser *parser = new XercesDOMParser;
parser->setValidationScheme(XercesDOMParser::Val_Never);
parser->parse("test.xml");
// get the DOM representation
DOMDocument *doc = parser->getDocument();
// get the root element
DOMElement* root = doc->getDocumentElement();
// evaluate the xpath
DOMXPathResult* result=doc->evaluate(
XMLString::transcode("/abc/def"), // "/abc/def/text()[0]"
root,
NULL,
DOMXPathResult::ORDERED_NODE_SNAPSHOT_TYPE, //DOMXPathResult::ANY_UNORDERED_NODE_TYPE, //DOMXPathResult::STRING_TYPE,
NULL);
// look into the xpart evaluate result
result->snapshotItem(0);
std::cout<<StrX(result->getNodeValue()->getFirstChild()->getNodeValue())<<std::endl;;
XMLPlatformUtils::Terminate();
return 0;
But I really hate that:
result->getNodeValue()->getFirstChild()->getNodeValue()
Has it to be a node set instead of the exact node I want?
I tried other format of XPath such as "/abc/def/text()[0]", and "DOMXPathResult::STRING_TYPE". xerces always thrown exception.
What did I do wrong?
I don't code with Xerces C++ but it seems to implement the W3C DOM Level 3 so based on that I would suggest to select an element node with a path like /abc/def and then simply to access result->getNodeValue()->getTextContent() to get the contents of the element (e.g. AAA BBB CCC).
As far as I understand the DOM APIs, if you want a string value then you need to use a path like string(/abc/def) and then result->getStringValue() should do (if the evaluate method requests any type or STRING_TYPE as the result type).
Other approaches if you know you are only interested in the first node in document order you could evaluate /abc/def with FIRST_ORDERED_NODE_TYPE and then access result->getNodeValue()->getTextContent().