Central to XMLUnit's XPath support is the
XpathEngine interface which consists of only
three methods:
/**
* Execute the specified xpath syntax <code>select</code> expression
* on the specified document and return the list of nodes (could have
* length zero) that match
* @param select
* @param document
* @return list of matching nodes
*/
NodeList getMatchingNodes(String select, Document document)
throws XpathException;
/**
* Evaluate the result of executing the specified XPath syntax
* <code>select</code> expression on the specified document
* @param select
* @param document
* @return evaluated result
*/
String evaluate(String select, Document document)
throws XpathException;
/**
* Establish a namespace context.
*/
void setNamespaceContext(NamespaceContext ctx);
The first two methods expect an XPath expression that
selects content from the DOM document that is the second
argument. The result of the selection can be either a DOM
NodeList or a String. The
later form tries to flatten the result, the value is said to be
"String-ified".
The third method is part of XMLUnit's support for XML Namespaces in XPath expressions. See Section 5.2, “Using XML Namespaces in XPath Selectors” for more details.
There are two implementations of the interface,
org.custommonkey.xmlunit.SimpleXpathEngine
and
org.custommonkey.xmlunit.jaxp13.Jaxp13XpathEngine.
The first implementation is the only one available in XMLUnit
1.0 and uses the configured JAXP XSLT
transformer. The second is new to XMLUnit 1.1 and only
available if JAXP 1.3 or later is supported, which should be the
case for Java 5 and later.
XpathException is an
Exception that will be thrown for invalid
XPath expressions or other problems with the underlying XPath
engine. It will typically wrap a
javax.xml.xpath.XPathExpressionException in
the Jaxp13XpathEngine case or a
javax.xml.transform.TransformerException when
SimpleXpathEngine is used.
The XMLUnit.newXpathEngine method will
first try to create an instance of
Jaxp13XpathEngine and fall back to
SimpleXpathEngine if JAXP 1.3 is not
supported.
One example of using the XPath support is included inside
it org.custommonkey.xmlunit.examples package.
It asserts that the string-ified form of an XPath selection
matches a regular expression. The code needed for this
is:
Example 31. Matching an XPath Selection Against a Regular Expression
XpathEngine engine = XMLUnit.newXpathEngine();
String value = engine.evaluate(xpath, doc);
Assert.assertTrue(message, value.matches(regex));
Starting with XMLUnit 1.1 XML Namespaces are supported for XPath queries.
The NamespaceContext interface provides
a mapping from prefix to namespace URI and is used by the XPath
engine. XPath selections then use the mapping's prefixes where
needed. Note that a prefix used in the document under test and
a prefix given as part of the
NamespaceContext are not related at all; the
context only applies to the XPath expression, the prefix used in
the document is ignored completely.
Right now XMLUnit provides only a single implementation of
the NamespaceContext interface:
SimpleNamespaceContext. This implementation
expects a java.util.Map as its constructor
argument. The Map must contain
(String) prefixes as keys and
(String) namespace URIs as values.
Note there is nothing like a default namespace in XPath selectors. If you are using namespaces in your XPath, all namespaces need a prefix (of length greater than zero). This is independent of the prefixes used in your document.
The following example is taken from XMLUnit's own tests. It demonstrates that the namespace prefix of the document under test is irrelevant and shows how to set up the namespace context.
Example 32. Using Namespaces in XPath Tests
String testDoc = "<t:test xmlns:t=\"urn:foo\"><t:bar/></t:test>";
Document d = XMLUnit.buildControlDocument(testDoc);
HashMap m = new HashMap();
m.put("foo", "urn:foo");
NamespaceContext ctx = new SimpleNamespaceContext(m);
XpathEngine engine = XMLUnit.newXpathEngine();
engine.setNamespaceContext(ctx);
NodeList l = engine.getMatchingNodes("//foo:bar", d);
assertEquals(1, l.getLength());
assertEquals(Node.ELEMENT_NODE, l.item(0).getNodeType());
It is possible to set a global
NamespaceContext, see Section 5.4, “Configuration Options” for details.
XMLTestCase and
XMLAssert provide several overloads for the
following common types of assertions:
NodeList as result:
assertXpathsEqual. There are methods that
use two different expressions on the same document and others
that compare expressions selecting from two different
documents.
The NodeLists are wrapped into a
surrogate root XML element and the resulting DOM
Documents are compared using
Diff.similar().
assertXpathsNotEqual.assertXpathValuesEqual. There are methods
that use two different expressions on the same document and
others that compare expressions selecting from two different
documents.assertXpathValuesNotEqual.assertXpathEvaluatesTo.NodeList selected by an XPath
expression is not empty:
assertXpathExists.NodeList selected by an XPath
expression is empty:
assertXpathNotExists.Neither method provides any control over the message of
the AssertionFailedError in case of a
failure.
When using XpathEngine directly you are
responsible for creating the DOM document yourself. If you use
the convenience methods of XMLTestCase or
XMLAssert you have several options to specify
the input; XMLUnit will use the control or test parser that has
been configured (see Section 2.4.1, “JAXP”) to create a DOM
document from the given piece of XML in that case - using the
configured EntityResolver(s) (see Section 2.4.2, “EntityResolver”) if any.
If JAXP 1.3 is not available,
SimpleXpathEngine will use the configured
JAXP XSLT transformer (see Section 2.4.1, “JAXP”) under the
covers.
When using JAXP 1.3 you can chose the actual
XPathFactory implementation using
XMLUnit.setXPathFactory.
It is possible to establish a global
NamespaceContext with the help of the
XMLUnit.setXpathNamespaceContext method. Any
XpathEngine created by
XMLUnit.newXpathEngine will automatically use
the given context. Note that the JUnit 3 convenience methods
use XMLUnit.newXpathEngine implicitly and
will thus use the configured
NamespaceContext.