XPath Support - xmlunit/user-guide GitHub Wiki
The (I)XPathEngine
interface inside the xpath/XPath
package/namespace provides two pairs of methods that apply an XPath
expression to a piece of XML and return the result as a string or
collection of DOM nodes.
The first pair expects the "piece of XML" to be provided as
(I)Source
like
most other code in XMLUnit as well, the
second pair directly works on DOM nodes. Initially only the
Source
-arg versions existed, but the underlying implementations can
work efficiently on nodes directly, so the overloads have been added.
For Java as well as .NET only a single implementation of the
XPathEngine
interface exists - they use javax.xml.xpath
and
System.Xml.XPath
under the covers directly. In the Java case all
checked exceptions will be
transformed to runtime exceptions.
In order to use namespace prefixes inside the XPath expressions you need to provide a NamespaceContext.
Selecting nodes:
Iterable<Node> i = new JAXPXPathEngine().selectNodes("//li", source);
assertNotNull(i);
int count = 0;
for (Iterator<Node> it = i.iterator(); it.hasNext(); ) {
count++;
assertEquals("li", it.next().getNodeName());
}
assertEquals(4, count);
in Java. For .NET it would be
IEnumerable<XmlNode> i = new XPathEngine().SelectNodes("//li", source);
Assert.IsNotNull(i);
int count = 0;
foreach (XmlNode n in i) {
count++;
Assert.AreEqual("li", n.Name);
}
Assert.AreEqual(4, count);
Evaluating an XPath to a string:
assertEquals("Don't blame it on the...",
new JAXPXPathEngine().evaluate("//title", source));
in Java. For .NET it would be
Assert.AreEqual("Don't blame it on the...",
new XPathEngine().Evaluate("//title", source));
XMLUnit for Java provides a Hamcrest macher named HasXPathMatcher
and XMLUnit.NET NUnit
(2.x and 3.x) constraints HasXPathConstraint
that
can be used to verify if a XPath expression corresponds to at least one
element in the provided XML input.
The Java Hamcrest matcher can be used in the following manner :
import static org.xmlunit.matchers.HasXPathMatcher.hasXPath;
import static org.hamcrest.core.IsNot.not;
...
final String xml = "<a><b attr=\"abc\"></b></a>";
assertThat(xml, hasXPath("//a/b/@attr"));
assertThat(xml, not(hasXPath("//a/b/c")));
The corresponding .NET code would be
string xml = "<a><b attr=\"abc\"></b></a>";
Assert.That(xml, HasXPathConstraint.HasXPath("//a/b/@attr"));
Assert.That(xml, !HasXPathConstraint.HasXPath("//a/b/c"));
The matcher and constraints also provide XML NamespaceContext support using the withNamespaceContext
method :
import static org.xmlunit.matchers.HasXPathMatcher.hasXPath;
...
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<feed xmlns=\"http://www.w3.org/2005/Atom\">" +
" <title>title</title>" +
" <entry>" +
" <title>title1</title>" +
" <id>id1</id>" +
" </entry>" +
"</feed>";
HashMap<String, String> prefix2Uri = new HashMap<String, String>();
prefix2Uri.put("atom", "http://www.w3.org/2005/Atom");
assertThat(xmlRootElement,
hasXPath("//atom:feed/atom:entry/atom:id").withNamespaceContext(prefix2Uri));
XMLUnit for Java provides a Hamcrest macher named EvaluateXPathMatcher
and XMLUnit.NET NUnit (2.x and 3.x) constraints EvaluateXPathConstraint
that
can be used to verify if the evaluation of the provided XPath expression
corresponds to the value matcher specified for the provided input XML object.
The Java Hamcrest matcher can be used in the following manner :
import static org.xmlunit.matchers.EvaluateXPathMatcher.hasXPath;
import static org.hamcrest.CoreMatchers.equalTo;
...
final String xml = "<a><b attr=\"abc\"></b></a>";
assertThat(xml, hasXPath("//a/b/@attr", equalTo("abc")));
assertThat(xml, hasXPath("count(//a/b/c)", equalTo("0")));
The corresponding .NET code would be
string xml = "<a><b attr=\"abc\"></b></a>";
Assert.That(xml, EvaluateXPathConstraint.HasXPath("//a/b/@attr", Is.EqualTo("abc")));
Assert.That(xml, EvaluateXPathConstraint.HasXPath("count(//a/b/c)", Is.EqualTo("0")));
The matcher and constraints also provide XML NamespaceContext support using the withNamespaceContext
method :
import static org.xmlunit.matchers.EvaluateXPathMatcher.hasXPath;
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<feed xmlns=\"http://www.w3.org/2005/Atom\">" +
" <title>title</title>" +
" <entry>" +
" <title>title1</title>" +
" <id>id1</id>" +
" </entry>" +
"</feed>";
HashMap<String, String> prefix2Uri = new HashMap<String, String>();
prefix2Uri.put("atom", "http://www.w3.org/2005/Atom");
assertThat(xmlRootElement,
hasXPath("//atom:feed/atom:entry/atom:id/text()", equalTo("id1"))
.withNamespaceContext(prefix2Uri));
XMLUnit for Java provides AssertJ assert classes named MultipleNodeAssert
and SingleNodeAssert
that can be used to verify if a XPath expression corresponds to at least one element in the provided XML input
and perform assertions on those elements.
Access to MultipleNodeAssert is provided by XmlAssert.nodesByXPath(XPath)
.
Using first()
, last()
, element()
on MultipleNodeAssert navigates to corresponding element node
and allow to perform assertions provided by SingleNodeAssert.
There are two XMLUnit modules supporting AssertJ. xmlunit-assertj
supports AssertJ 2.x and 3.x while xmlunit-assertj3
requires at
least AssertJ 3.18.1. The examples here assume xmlunit-assertj
, with
xmlunit-assertj3
the Java package has to be changed to
org.xmlunit.assertj3
.
import static org.xmlunit.assertj.XmlAssert.assertThat;
...
final String xml = "<a><b attr=\"abc\"></b></a>";
assertThat(xml).nodesByXPath("//a/b/@attr") // MultipleNodeAssert type
.exist();
assertThat(xml).hasXPath("//a/b/@attr");
assertThat(xml).nodesByXPath("//a/b/c") // MultipleNodeAssert type
.doNotExist();
assertThat(xml).doesNotHaveXPath("//a/b/c");
import static org.xmlunit.assertj.XmlAssert.assertThat;
final String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<feed xmlns=\"http://www.w3.org/2005/Atom\">" +
" <title>title</title>" +
" <entry>" +
" <title>title1</title>" +
" <id>id1</id>" +
" </entry>" +
"</feed>";
final Map<String, String> prefix2Uri = new HashMap<String, String>();
prefix2Uri.put("atom", "http://www.w3.org/2005/Atom");
assertThat(xml)
.withNamespaceContext(prefix2Uri)
.hasXPath("//atom:feed/atom:entry/atom:id");
import static org.xmlunit.assertj.XmlAssert.assertThat;
final String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<feed>" +
" <title>title</title>" +
" <entry attr=\"abc\" attr2=\"value\" foo=\"bar\">" +
" <title>title1</title>" +
" </entry>" +
" <entry attr=\"xyz\" attr2=\"value\">" +
" <title>title1</title>" +
" </entry>" +
"</feed>";
assertThat(xml).nodesByXPath("/feed/entry")
.haveAttribute("attr")
.haveAttribute("attr2", "value");
assertThat(xml).nodesByXPath("/feed/entry") // MultipleNodeAssert type
.first() // SingleNodeAssert type
.hasAttribute("foo");
assertThat(xml).nodesByXPath("/feed/entry") // MultipleNodeAssert type
.last() // SingleNodeAssert type
.doesNotHaveAttribute("foo");
assertThat(xml).nodesByXPath("/feed/entry") // MultipleNodeAssert type
.element(1) // SingleNodeAssert type
.doesNotHaveAttribute("foo");
XMLUnit for Java 7+ provides an AssertJ assert class named ValueAssert
that can be used to verify if the evaluation of the provided XPath expression
meets certain conditions.
Access to ValueAssert
is provided by XmlAssert.valueByXPath(XPath)
.
import static org.xmlunit.assertj.XmlAssert.assertThat;
final String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<fruits>" +
" <fruit name=\"apple\"/>" +
" <fruit name=\"orange\"/>" +
" <fruit name=\"banana\"/>" +
"</fruits>";
assertThat(xml).valueByXPath("count(//fruits/fruit)").isEqualTo("3");
assertThat(xml).valueByXPath("count(//fruits/fruit)").isEqualTo(3);
assertThat(xml).valueByXPath("count(//fruits/fruit[@name=\"orange\"])").isEqualTo(1);
assertThat(xml).valueByXPath("count(//fruits/fruit[@name=\"apricot\"])").isEqualTo(0);
assertThat(xml).valueByXPath("count(//fruits/fruit)") // ValueAssert type
.asInt() // org.assertj.core.api.AbstractIntegerAssert type
.isEqualTo(3)
.isGreaterThan(2);
final String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<fruits>" +
" <fruit name=\"apple\" weight=\"66.6\"/>" +
"</fruits>";
assertThat(xml).valueByXPath("//fruits/fruit/@weight") // ValueAssert type
.asDouble() // org.assertj.core.api.AbstractDoubleAssert type
.isEqualTo(66.6)
.isPositive();
final String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<fruits>" +
" <fruit name=\"apple\" fresh=\"True\"/>" +
" <fruit name=\"pear\" fresh=\"0\"/>" +
"</fruits>";
assertThat(xml).valueByXPath("//fruits/fruit[@name=\"apple\"]/@fresh") // ValueAssert type
.asBoolean() // org.assertj.core.api.AbstractBooleanAssert type
.isTrue();
assertThat(xml).valueByXPath("//fruits/fruit[@name=\"pear\"]/@fresh").asBoolean().isFalse();
final String xml ="<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<a><b>" +
"<![CDATA[<c><d attr=\"xyz\"></d></c>]]>" +
"</b></a>";
assertThat(xml).valueByXPath("//a/b/text()") // ValueAssert type
.isEqualTo("<c><d attr=\"xyz\"></d></c>")
.asXml() // XmlAssert type
.hasXPath("/c/d");
import static org.xmlunit.assertj.XmlAssert.assertThat;
final String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<feed xmlns=\"http://www.w3.org/2005/Atom\">" +
" <title>title</title>" +
" <entry>" +
" <title>title1</title>" +
" <id>id1</id>" +
" </entry>" +
"</feed>";
final Map<String, String> prefix2Uri = new HashMap<String, String>();
prefix2Uri.put("atom", "http://www.w3.org/2005/Atom");
assertThat(xml)
.withNamespaceContext(prefix2Uri)
.valueByXPath("//atom:feed/atom:entry/atom:id/text()")
.isEqualTo("id1");
Some XPath implementations allow the use of extension functions to add new functions that can be used in XPath expressions. These functions can invoke user provided code.
JAXP's XPathFactory
by default allows the execution of extension
functions and so did the default XPath
used internally prior to
XMLUnit for Java 2.10.0. Starting with 2.10.0 the default has been
changed to disable extension functions - at least when using Java 18
or later.
This means if you want to protect yourself against extension functions
and you use a version of XMLUnit prior to 2.10.0 you have to
explicitly set an XPathFactory
that is configured properly. Likewise
if you rely on extension functions you must provide an explicit
XPathFactory
when using XMLUnit 2.10.0 or later.
Please see JAXP Security Guide for details.
Prior to Java 18 there is no way to configure properties for an
XPathFactory
instance, here you may need to set global system
properties or enable the secure processing feature on the factory.
´XPathNavigator` doesn't support extension functions at all.