5. XPath Tests

5.1. XPath Engines

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

5.2. Using XML Namespaces in XPath Selectors

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.

5.3. JUnit 3.x Convenience Methods

XMLTestCase and XMLAssert provide several overloads for the following common types of assertions:

  • Two XPath expression should return the same DOM 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().

  • The opposite of the above, the expressions should yield different results: assertXpathsNotEqual.
  • Two XPath expression should return the same "String-ified" result: assertXpathValuesEqual. There are methods that use two different expressions on the same document and others that compare expressions selecting from two different documents.
  • The opposite of the above, the expressions should yield different results: assertXpathValuesNotEqual.
  • The XPath expression should return an expected value when "String-ified" or interpreted as qualified name: assertXpathEvaluatesTo.
  • The NodeList selected by an XPath expression is not empty: assertXpathExists.
  • The 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.

5.4. Configuration Options

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.