Using XPath
Some time ago in this document we have already seen how XML message payloads are constructed when sending and receiving messages. Now using XPath is a very powerful way of accessing elements in complex XML structures. The XPath expression language is very handy when it comes to save element values as test variables or when validating special elements in a XML message structure.
XPath is a very powerful technology for walking XML trees. This W3C standard stands for advanced XML tree handling using a special syntax as query language. Citrus supports the XPath syntax in the following fields:
- <message><element path="[XPath-Expression]"></message>
- <validate><xpath expression="[XPath-Expression]"/></validate>
- <extract><message path="[XPath-Expression]"></extract>
- <ignore path="[XPath-Expression]"/>
The next program listing indicates the power in using XPath with Citrus:
<message>
<validate>
<xpath expression="//User/Name" value="John"/>
<xpath expression="//User/Address[@type='office']/Street" value="Companystreet 21"/>
<xpath expression="//User/Name" value="${userName}"/>
<xpath expression="//User/@isAdmin" value="${isAdmin}"/>
<xpath expression="//User/@isAdmin" value="true" result-type="boolean"/>
<xpath expression="//*[.='search-for']" value="searched-for"/>
<xpath expression="count(//orderStatus[.='success'])" value="3" result-type="number"/>
</validate>
</message>
Now we describe the XPath usage in Citrus step by step.
Manipulate with XPath
Some elements in XML message payloads might be of dynamic nature. Just think of generated identifiers or timestamps. Also we do not want to repeat the same static identifier several times in our test cases. This is the time where test variables and dynamic message element overwrite come in handy. The idea is simple. We want to overwrite a specific message element in our payload with a dynamic value. This can be done with XPath or inline variable declarations. Lets have a look at an example listing showing both ways:
XML DSL
<message>
<payload>
<TestMessage>
<MessageId>${messageId}</MessageId>
<CreatedBy>_</CreatedBy>
<VersionId>${version}</VersionId>
</TestMessage>
</payload>
<element path="/TestMessage/CreatedBy" value="${user}"/>
</message>
The program listing above shows ways of setting variable values inside a message template. First of all you can simply place variable expressions inside the message (see how ${messageId} is used). In addition to that you can also use XPath expressions to explicitly overwrite message elements before validation.
<element path="/TestMessage/CreatedBy" value="${user}"/>
The XPath expression evaluates and searches for the right element in the message payload. The previously defined variable ${user} replaces the element value. Of course this works with XML attributes too.
Both ways via XPath or inline variable expressions are equal to each other. With respect to the complexity of XML namespaces and XPath you may find the inline variable expression more comfortable to use. Anyway feel free to choose the way that fits best for you. This is how we can add dynamic variable values to the control template in order to increase maintainability and robustness of message validation.
Tip Validation matchers put validation mechanisms to a new level offering dynamic assertion statements for validation. Have a look at the possibilities with assertion statements invalidation-matchers### Validate with XPath
We have already seen how to validate whole XML structures with control message templates. All elements are validated and compared one after another. In some cases this approach might be too extensive. Imagine the tester only needs to validate a small subset of message elements. The definition of control templates in combination with several ignore statements is not appropriate in this case. You would rather want to use explicit element validation.
XML DSL
<message>
<validate>
<xpath expression="/TestRequest/MessageId" value="${messageId}"/>
<xpath expression="/TestRequest/VersionId" value="2"/>
</validate>
</message>
Java DSL designer
@CitrusTest
public void receiveMessageTest() {
receive("helloServiceServer")
.validate("/TestRequest/MessageId", "${messageId}")
.validate("//VersionId", "2")
.header("Operation", "sayHello");
}
Instead of comparing the whole message some message elements are validated explicitly via XPath. Citrus evaluates the XPath expression on the received message and compares the result value to the control value. The basic message structure as well as all other message elements are not included into this explicit validation.
Note
If this type of element validation is chosen neither
Tip Citrus offers an alternative dot-notated syntax in order to walk through XML trees. In case you are not familiar with XPath or simply need a very easy way to find your element inside the XML tree you might use this way. Every element hierarchy in the XML tree is represented with a simple dot - for example:
TestRequest.VersionId
The expression will search the XML tree for the respective
Of course this dot-notated syntax is very simple and might not be applicable for more complex tree navigation. XPath is much more powerful - no doubt. However the dot-notated syntax might help those of you that are not familiar with XPath. So the dot-notation is supported wherever XPath expressions might apply.
The Xpath expressions can evaluate to different result types. By default Citrus is operating on NODE and STRING result types so that you can validate some element value. But you can also use different result types such as NODESET and BOOLEAN . See this example how that works:
XML DSL
<message>
<validate>
<xpath expression="/TestRequest/Error" value="false" result-type="boolean"/>
<xpath expression="/TestRequest/Status[.='success']" value="3" result-type="number"/>
<xpath expression="/TestRequest/OrderType" value="[single, multi, multi]" result-type="node-set"/>
</validate>
</message>
Java DSL designer
@CitrusTest
public void receiveMessageTest() {
receive("helloServiceServer")
.validate("boolean:/TestRequest/Error", false)
.validate("number:/TestRequest/Status[.='success']", 3)
.validate("node-set:/TestRequest/OrderType", "[single, multi, multi]")
.header("Operation", "sayHello");
}
In the example above we use different expression result types. First we want to make sure nor /TestRequest/Error element is present. This can be done with a boolean result type and false value. Second we want to validate the number of found elements for the expression /TestRequest/Status[.='success'] . The XPath expression evaluates to a node list that results in its list size to be checked. And last not least we evaluate to a node-set result type where all values in the node list will be translated to a comma delimited string value.
Now lets have a look at some more powerful validation expressions using matcher implementations. Up to now we have seen that XPath expression results are comparable with equalTo operations. We would like to add some more powerful validation such as greaterThan, lessThan, hasSize and much more. Therefore we have introduced Hamcrest validation matcher support in Citrus. Hamcrest is a very poweful matcher library that provides a fantastic set of matcher implementations. Lets see how we can add these in our test case:
XML DSL
<message>
<validate>
<xpath expression="/TestRequest/Error" value="@assertThat(anyOf(empty(), nullValue()))@"/>
<xpath expression="/TestRequest/Status[.='success']" value="@assertThat(greaterThan(0))@" result-type="number"/>
<xpath expression="/TestRequest/OrderType" value="@assertThat(hasSize(3))@" result-type="node-set"/>
</validate>
</message>
Java DSL designer
@CitrusTest
public void receiveMessageTest() {
receive("helloServiceServer")
.validate("/TestRequest/Error", anyOf(empty(), nullValue()))
.validate("number:/TestRequest/Status[.='success']", greaterThan(0))
.validate("node-set:/TestRequest/OrderType", hasSize(3))
.header("Operation", "sayHello");
}
When using the XML DSL we have to use the assertThat validation matcher syntax for defining the Hamcrest matchers. You can combine matcher implementation as seen in the anyOf(empty(), nullValue()) expression. When using the Java DSL you can just add the matcher as expected result object. Citrus evaluates the matchers and makes sure everything is as expected. This is a very powerful validation mechanism as it also works with node-sets containing multiple values as list.
This is how you can add very powerful message element validation in XML using XPath expressions.
Extract variables with XPath
Imagine you receive a message in your test with some generated message identifier values. You have no chance to predict the identifier value because it was generated at runtime by a foreign application. You can ignore the value in order to protect your validation. But in many cases you might need to return this identifier in the respective response message or somewhat later on in the test. So we have to save the dynamic message content for reuse in later test steps. The solution is simple and very powerful. We can extract dynamic values from received messages and save those to test variables. Add this code to your message receiving action.
XML DSL
<extract>
<header name="Operation" variable="operation"/>
<message path="/TestRequest/VersionId" variable="versionId"/>
</extract>
Java DSL designer
@CitrusTest
public void receiveMessageTest() {
receive("helloServiceServer")
.extractFromHeader("Operation", "operation")
.extractFromPayload("//TestRequest/VersionId", "versionId");
echo("Extracted operation from header is: ${operation}");
echo("Extracted version from payload is: ${versionId}");
}
As you can see Citrus is able to extract both header and message payload content into test variables. It does not matter if you use new test variables or existing variables as target. The extraction will automatically create a new variable in case it does not exist. The time the variable was created all following test actions can access the test variables as usual. So you can reference the variable values in response messages or other test steps ahead.
Tip We can also use expression result types in order to manipulate the test variable outcome. In case we use a boolean result type the existence of elements can be saved to variable values. The result type node-set translates a node list result to a comma separated string of all values in this node list. Simply use the expression result type attributes as shown in previous sections.
XML namespaces in XPath
When it comes to XML namespaces you have to be careful with your XPath expressions. Lets have a look at an example message that uses XML namespaces:
<ns1:TestMessage xmlns:ns1="http://citrus.com/namespace">
<ns1:TestHeader>
<ns1:CorrelationId>_</ns1:CorrelationId>
<ns1:Timestamp>2001-12-17T09:30:47.0Z</ns1:Timestamp>
<ns1:VersionId>2</ns1:VersionId>
</ns1:TestHeader>
<ns1:TestBody>
<ns1:Customer>
<ns1:Id>1</ns1:Id>
</ns1:Customer>
</ns1:TestBody>
</ns1:TestMessage>
Now we would like to validate some elements in this message using XPath
<message>
<validate>
<xpath expression="//TestMessage/TestHeader/VersionId" value="2"/>
<xpath expression="//TestMessage/TestHeader/CorrelationId" value="${correlationId}"/>
</validate>
</message>
The validation will fail although the XPath expression looks correct regarding the XML tree. Because the message uses the namespace xmlns:ns1="http://citrus.com/namespace" with its prefix ns1 our XPath expression is not able to find the elements. The correct XPath expression uses the namespace prefix as defined in the message.
<message>
<validate>
<xpath expression="//ns1:TestMessage/ns1:TestHeader/ns1:VersionId" value="2"/>
<xpath expression="//ns1:TestMessage/ns1:TestHeader/ns1:CorrelationId" value="${correlationId}"/>
</message>
Now the expressions work fine and the validation is successful. But this is quite error prone. This is because the test is now depending on the namespace prefix that is used by some application. As soon as the message is sent with a different namespace prefix (e.g. ns2) the validation will fail again.
You can avoid this effect when specifying your own namespace context and your own namespace prefix during validation.
<message>
<validate>
<xpath expression="//pfx:TestMessage/pfx:TestHeader/pfx:VersionId" value="2"/>
<xpath expression="//pfx:TestMessage/pfx:TestHeader/pfx:CorrelationId" value="${correlationId}"/>
<namespace prefix="pfx" value="http://citrus.com/namespace"/>
</validate>
</message>
Now the test in independent from any namespace prefix in the received message. The namespace context will resolve the namespaces and find the elements although the message might use different prefixes. The only thing that matters is that the namespace value (http://citrus.com/namespace) matches.
Tip Instead of this namespace context on validation level you can also have a global namespace context which is valid in all test cases. We just add a bean in the basic Spring application context configuration which defines global namespace mappings.
<namespace-context>
<namespace prefix="def" uri="http://www.consol.de/samples/sayHello"/>
</namespace-context>
Once defined the def namespace prefix is valid in all test cases and all XPath expressions. This enables you to free your test cases from namespace prefix bindings that might be broken with time. You can use these global namespace mappings wherever XPath expressions are valid inside a test case (validation, ignore, extract).
Default namespaces in XPath
In the previous section we have seen that XML namespaces can get tricky with XPath validation. Default namespaces can do even more! So lets look at the example with default namespaces:
<TestMessage xmlns="http://citrus.com/namespace">
<TestHeader>
<CorrelationId>_</CorrelationId>
<Timestamp>2001-12-17T09:30:47.0Z</Timestamp>
<VersionId>2</VersionId>
</TestHeader>
<TestBody>
<Customer>
<Id>1</Id>
</Customer>
</TestBody>
</TestMessage>
The message uses default namespaces. The following approach in XPath will fail due to namespace problems.
<message>
<validate>
<xpath expression="//TestMessage/TestHeader/VersionId" value="2"/>
<xpath expression="//TestMessage/TestHeader/CorrelationId" value="${correlationId}"/>
</validate>
</message>
Even default namespaces need to be specified in the XPath expressions. Look at the following code listing that works fine with default namespaces:
<message>
<validate>
<xpath expression="//:TestMessage/:TestHeader/:VersionId" value="2"/>
<xpath expression="//:TestMessage/:TestHeader/:CorrelationId" value="${correlationId}"/>
</validate>
</message>
Tip It is recommended to use the namespace context as described in the previous chapter when validating. Only this approach ensures flexibility and stable test cases regarding namespace changes.