XPath in soapUI Part 3: Common XPath Functions

XPath supports quite a few functions that can be used to make your soapUI assertions even more sophisticated, including functions for different data types, aggregate functions to perform calculations across multiple values, etc.  In this post I'll look at a few basic functions with some examples; for a more extensive list I recommend this page at w3schools.com.  The examples reference the same sample project used in the last few posts, targeting a web service that returns data about holidays; you can download the project here if you haven't done so already.   The post also assumes you're familiar with namespaces, namespace prefixes, and basic XPath syntax, covered in previous posts.

Count() and Numeric Functions

The count() function (not too surprisingly) counts the number of matches found for a given expression.  For example, the following XPath expression counts the number of Holiday elements returned in the GetHolidaysAvailable test response; we could use it in an assertion to confirm that 24 holidays are returned:

count(//Holidays)

There are several numeric functions similar to the count() function that perform operations on expressions that return multiple matches.  For example, Holidays elements returned by the GetHolidaysAvailable test request have a numeric rowOrder attribute.  We can use the following functions to find their minimum, maximum, average, and sum:

min(//Holidays/@msdata:rowOrder)   (returns 0.0)
max(//Holidays/@msdata:rowOrder)   (returns 23.0)
avg(//Holidays/@msdata:rowOrder)   (returns 11.5)
sum(//Holidays/@msdata:rowOrder)   (returns 276.0)

String Functions

There are quite a few XPath functions for working with string data.  The contains(), starts-with(), and ends-with() functions each take two strings as their arguments; the first string (typically the result of an XPath expression) is tested accordingly for the presence of the second string.  For example, run the following XPath assertion against the GetHolidaysAvailable response:

contains(//Holidays[6]/Name , 'Patrick')   (returns true)

The XPath expression //Holidays[6]/Name evaluates to "St. Patrick's Day" (the Name of the sixth Holidays element in the response).  As the first argument to the contains() function, the string "St. Patrick's Day" is checked to see if it contains the second argument, the string "Patrick".  Consequently, the function returns the Boolean true.

Some example expressions using the starts-with() and ends-with() functions:

starts-with(//Holidays[8]/Key , 'GOOD_')   (returns true)
ends-with(//Holidays[13]/Name , 'Mayo')   (returns true)

Date Functions

XPath also provides some functions for working with dates, including functions to extract individual date/time components (months, years, minutes, etc.) from date and time values.  Here are examples we could use with the GetHolidaysForYear test request to confirm the month and day of Cinco de Mayo:

month-from-dateTime(//Holidays[Name = 'Cinco de Mayo']/Date)   (returns 5)
day-from-dateTime(//Holidays[Name = 'Cinco de Mayo']/Date)   (returns 5)

There are corresponding functions to retrieve the year, hours, minutes, or seconds from a full date and time value-- year-from-dateTime(), minutes-from-dateTime(), etc.-- plus functions to do the same with date- or time-only values-- year-from-date(), month-from-date(), hours-from-time(), seconds-from-time(), and so on.

Not() Function

In the last post, we saw that it's possible to use the logical operators and and or with XPath expressions.  The not() logical function takes an expression that results in a Boolean value and negates it, returning true if the original expression evaluated to false or false if the original expression evaluated to true. Not() is frequently used with the count() function to check that a given condition doesn't exist in test response XML.

For example, the GetHolidaysForYear test request returns holidays for a specified year.  One check we might want to do is to confirm that all of the dates are indeed within the specified year.  We know that no matter how many holidays are returned, zero holidays should be returned that don't fall in the specified year (we'll use 2013 in this case).  Here's a function that uses count(), not(), starts-with(), and logical operators to confirm that at least one holiday is returned (to make sure we're not just counting zero elements to begin with-- which would indicate something else is going wrong) and none of them fall outside 2013:

count(//Holidays) > 0 and count(//Holidays[not(starts-with(Date , '2013'))]) = 0   (returns true)

The first half of the assertion (before the and operator) should be pretty self-explanatory, but let's analyze the second part from the inside out, beginning with the starts-with() function, which checks the value of the Date element (as a string) to see if it begins with "2013".  The result of the starts-with function, a Boolean value, is negated by the not() function.  All of this is used as a predicate (within brackets) to target certain Holidays elements-- i.e., return only those Holidays elements with Date child elements that don't start with "2013".  Finally, matches found for that expression are counted by the count() function.  As we expected, the result of the count() function is 0-- none of our Holidays elements have dates that don't fall in 2013.

The example illustrates one of the potential pitfalls when working with longer, more complex expressions and multiple functions-- navigating the tangle of brackets and parentheses.  If you receive an "Invalid XPath expression" error trying to evaluate a complex expression like this, be sure to double-check the order and count (always in opening and closing pairs) of parentheses and brackets.  Also, if you get stuck building an expression with nested functions, you may find it helpful to back up and try working from the inside out, if possible, checking to make sure each part works by itself before putting all the pieces together.

In the next post, we'll look at using XPath with property transfers in soapUI.

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).