Sometimes it is easier to test a piece of XML's validity by traversing the whole document node by node and test each node individually. Maybe there is no control XML to validate against or the expected value of an element's content has to be calculated. There may be several reasons.
XMLUnit supports this approach of testing via the
NodeTest
class. In order to use it, you need a
DOM implementation that generates Document
instances that implement the optional
org.w3c.traversal.DocumentTraversal
interface,
which is not part of JAXP's standardized DOM support.
As of the release of XMLUnit 1.1 the
Document
instances created by most parsers
implement DocumentTraversal
, this includes
but is not limited to Apache Xerces, the parser shipping with
Sun's JDK 5 and later or GNU JAXP. One notable exception is
Apache Crimson, which also means the parser shipping with Sun's
JDK 1.4 does not support traversal; you need to specify a
different parser when using JDK 1.4 (see Section 2.4.1, “JAXP”).
You can test whether your XML parser supports
DocumentTraversal
by invoking
org.w3c.dom.DOMImplementation
's
hasFeature
method with the feature
"Traversal"
.
The NodeTest
is instantiated with a
piece of XML to traverse. It offers two
performTest
methods:
/** * Does this NodeTest pass using the specified NodeTester instance? * @param tester * @param singleNodeType note <code>Node.ATTRIBUTE_NODE</code> is not * exposed by the DocumentTraversal node iterator unless the root node * is itself an attribute - so a NodeTester that needs to test attributes * should obtain those attributes from <code>Node.ELEMENT_NODE</code> * nodes * @exception NodeTestException if test fails */ public void performTest(NodeTester tester, short singleNodeType); /** * Does this NodeTest pass using the specified NodeTester instance? * @param tester * @param nodeTypes note <code>Node.ATTRIBUTE_NODE</code> is not * exposed by the DocumentTraversal node iterator unless the root node * is itself an attribute - so a NodeTester that needs to test attributes * should obtain those attributes from <code>Node.ELEMENT_NODE</code> * nodes instead * @exception NodeTestException if test fails */ public void performTest(NodeTester tester, short[] nodeTypes);
NodeTester
is the class testing each
node and is described in the next section.
The second argument limits the tests on DOM
Node
s of (a) specific type(s).
Node
types are specified via the
static
fields of the Node
class. Any Node
of a type not specified as
the second argument to performTest
will be
ignored.
Unfortunately XML attributes are not exposed as
Node
s during traversal. If you need access
to attributes you must add Node.ELEMENT_NODE
to the second argument of performTest
and
access the attributes from their parent
Element
.
Example 33. Accessing Attributes in a
NodeTest
... NodeTest nt = new NodeTest(myXML); NodeTester tester = new MyNodeTester(); nt.performTest(tester, Node.ELEMENT_NODE); ... class MyNodeTester implements NodeTester { public void testNode(Node aNode, NodeTest test) { Element anElement = (Element) aNode; Attr attributeToTest = anElement.getAttributeNode(ATTRIBUTE_NAME); ... } ... }
Any entities that appear as part of the
Document
are expanded before the traversal
starts.
Implementations of the NodeTester
interface are responsible for the actual test:
/** * Validate a single Node * @param aNode * @param forTest * @exception NodeTestException if the node fails the test */ void testNode(Node aNode, NodeTest forTest) throws NodeTestException ; /** * Validate that the Nodes passed one-by-one to the <code>testNode</code> * method were all the Nodes expected. * @param forTest * @exception NodeTestException if this instance was expecting more nodes */ void noMoreNodes(NodeTest forTest) throws NodeTestException ;
NodeTest
invokes
testNode
for each Node
as
soon as it is reached on the traversal. This means
NodeTester
"sees" the
Node
s in the same order they appear within
the tree.
noMoreNodes
is invoked when the
traversal is finished. It will also be invoked if the tree didn't
contain any matched Node
s at all.
Implementations of NodeTester
are
expected to throw a NodeTestException
if the
current not doesn't match the test's expectations or more nodes
have been expected when noMoreNodes
is
called.
XMLUnit ships with two implementations of
NodeTest
that are described in the following
to sections.
AbstractNodeTester
implements
testNode
by testing the passed in
Node
for its type and delegating to one of
the more specific test...
Methods it adds.
By default the new test...
methods all
throw a NodeTestException
because of an
unexpected Node
.
It further implements noMoreNodes
with an empty method - i.e. it does nothing.
If you are only testing for specific types of
Node
it may be more convenient to subclass
AbstractNodeTester
. For example Example 33, “Accessing Attributes in a
NodeTest
” could be re-written as:
Example 34. Accessing Attributes in a
NodeTest
-
AbstractNodeTester
version
... NodeTest nt = new NodeTest(myXML); NodeTester tester = new AbstractNodeTester() { public void testElement(Element element) throws NodeTestException { Attr attributeToTest = element.getAttributeNode(ATTRIBUTE_NAME); ... } }; nt.performTest(tester, Node.ELEMENT_NODE); ...
Note that even though
AbstractNodeTester
contains a
testAttribute
method it will never be
called by default and you still need to access attributes via
their parent elements.
Note also that the root of the test is the document's
root element, so any Node
s preceding the
document's root Element
won't be visited
either. For this reason the
testDocumentType
,
testEntity
and
testNotation
methods are probably never
called either.
Finally, all entity references have been expanded before
the traversal started. EntityReference
s
will have been replaced by their replacement text if it is
available, which means testEntityReference
will not be called for them either. Instead the replacement
text will show up as (part of) a Text
node
or as Element
node, depending on the
entity's definition.
org.custommonkey.xmlunit.examples.CountingNodeTester
is a simple example NodeTester
that asserts
that a given number of Node
s have been
traversed. It will throw a
NodeTestException
when
noMoreNodes
is called before the expected
number of Node
s has been visited or the
actual number of nodes exceeded the expected count.
XMLAssert
and
XMLTestCase
contain overloads of
assertNodeTestPasses
methods.
The most general form of it expects you to create a
NodeTest
instance yourself and lets you
specify whether you expect the test to fail or to pass.
The other two overloads create a
NodeTest
instance from either
String
or a SAX
InputSource
and are specialized for the case
where you are only interested in a single Node
type and expect the test to pass.
Neither method provides any control over the message of
the AssertionFailedError
in case of a
failure.
The only configurable option for
NodeTest
is the XML parser used if the piece
of XML is not specified as a Document
or
DocumentTraversal
.
NodeTest
will use the "control" parser that
has been configured - see Section 2.4.1, “JAXP” for
details.
It will also use the EntityResolver
configured for the control parser if one has been set - see
Section 2.4.2, “EntityResolver
”.