XPath in soapUI Part 2: More Complex XPath Expressions

In the last post we looked at a simple example of an XPath assertion in soapUI-- in fact, we could have used a basic Contains assertion to accomplish pretty much the same thing.  In this post, we'll see a little more of the power of XPath and work on some more complex expressions.

Using the same sample project we used in the last post, let's look at the GetHolidaysForYear test case and test request.  Run it and look at the response XML:


An excerpt from the GetHolidaysForYear response XML

Where the GetHolidayDate request we looked at in the last post essentially returned one significant node, there are multiple nodes in which we could be interested here; consequently, there are some extra layers of complexity to deal with in our XPath expression.  Let's say we want to confirm the correct date is reflected for Valentine's Day in 2013, for example.  There are multiple Holidays elements; our XPath expression has to identify which one corresponds to Valentine's Day and check the value of its Date child element.

Create a new XPath assertion and click the Declare button to have soapUI automatically generate namespace declarations.  You may notice an odd thing here: one of our declared namespaces (for prefix "ns1") is an empty string:


Namespace declarations auto-generated by soapUI

If you look again at the response XML, you'll see there is a namespace defined (without a prefix) as an empty string in the NewDataSet opening tag.  This appears to be a negation of an earlier namespace definition in the GetHolidaysForYearResponse tag-- without this empty string namespace defined for the NewDataSet element, it would inherit the namespace defined for the GetHolidaysForYearResponse element ("http://www.27seconds.com/Holidays/").  The upshot: for the NewDataSet element (and any children without their own prefixes) we actually don't have to use any namespace prefix in our XPath expressions.

Now we have to figure out how to specify the Holidays element in our response that corresponds to Valentine's Day.  There are a few ways of doing this.  One pretty straightforward way is through the use of an index, placed within brackets after the Holidays element in our expression.  Note that with XPath, index numbers are 1-based as opposed to 0-based-- the first instance has index 1, the next has index 2, etc.  Valentine's Day is the 4th holiday reflected in the response, so we can construct our XPath assertion like this:

//Holidays[4]/Date

As you'll remember from the last post, "//Holidays" signifies that we're looking for a Holidays element anywhere (at any level in the tree) in the response XML.  The "[4]" following the name of the Holidays element references the fourth instance of the element occurring in the response.  Finally, "/Date" indicates that we're looking for the value of a Date element-- but note the use of a single-slash here instead of a double-slash: this limits our results to a Date element that's a child of the element before the slash.  The expression is equivalent to saying, "Return the value of the Date element that's a direct child of the fourth Holidays element."  Click the "Select from current" button to evaluate the expression, and it should return the expected date of "2013-02-14T00:00:00-05:00".

Of course, if an operation doesn't always return its data in the same order, using an index may not be sufficient.  You can use other types of expressions within the brackets to identify a particular occurrence of an element.  For example:

//Holidays[Name = "Valentine's Day"]/Date

This XPath expression isn't much different from the previous one (in fact, it evaluates to the same value), but instead of an index it uses a comparison operation within the brackets.  This expression finds the instance of a Holidays element that has a Name child element with the value "Valentine's Day", and then returns the value of the Date element for that instance.

You can also work with XML attributes, data specified within a tag as opposed to between matching tags.  For example, each Holidays element has a unique attribute called rowOrder that can be used to identify a particular instance.  We can use the following expression to get the Date value for Cinco de Mayo:

//Holidays[@msdata:rowOrder = "10"]/Date

Just as above, we're using the expression in the brackets to identify a particular instance of a Holidays element.  The "@" symbol indicates that we're looking for an attribute and not a standard element.  Note that we have to be careful with namespace prefixes here-- unlike the Holidays and Date elements, the rowOrder attribute has its own namespace prefix in the response XML and therefore requires a corresponding prefix ("msdata") in our XPath expression.

In addition to the "=" (equals) operator, XPath supports all the comparison operators you might expect: "!=" (not equals), ">" (greater than), "<" (less than), ">=" (greater than or equal to), and "<=" (less than or equal to).  Additionally, you can use these comparison operators in conjunction with "and" and "or"-- some examples:

//Holidays[@diffgr:id = "Holidays17" and @msdata:rowOrder <= 20]/Key   (evaluates to "LABOR")

//Holidays[@msdata:rowOrder >= 100 or @diffgr:id = "Holidays21"]/Name   (evaluates to "Thanksgiving")

In the next post we'll look at some common XPath functions.

No comments:

Post a Comment

Please be respectful of others (myself included!) when posting comments. Unfortunately, I may not be able to address (or even read) all comments immediately, and I reserve the right to remove comments periodically to keep clutter to a minimum ("clean" posts that aren't disrespectful or off-topic should stay on the site for at least 30 days to give others a chance to read them). If you're looking for a solution to a particular issue, you're free to post your question here, but you may have better luck posting your question on the main forum belonging to your tool's home site (links to these are available on the navigation bar on the right).