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());
In some cases the "stringified" value of an XPath
evaluation is a qualified name - a string that encodes a
namespace URI together with a local name. There are two common
formats for such qualified names, one used by Java5's
QName
in the format
{NS-URI}LOCAL-NAME
and one using
PREFIX:LOCAL-NAME
. Starting with XMLUnit 1.6
a new QualifiedName
class can parse either
representation. The assertXpathEvaluatesTo
overloads where the expected value is a
QualifiedName
try to parse the stringified
value in either format - using the documents namespace context
when parsing the actual value.
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 NodeList
s are wrapped into a
surrogate root XML element and the resulting DOM
Document
s 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
.