A basic XMLUnit test verifies that two XML strings are equivalent using the assert method assertXMLEqual( ) . The test class is derived from XMLTestCase , which also gives it access to the functionality of the base JUnit class TestCase . Example 10-1 illustrates such a test. Example 10-1. Testing for equivalent XML content XMLElementTest.java import org.custommonkey.xmlunit.*; public class XMLElementTest extends XMLTestCase { public void testEmptyElement ( ) throws Exception { XMLElement element = new XMLElement("test"); String expected = "<test></test>"; assertXMLEqual (expected, element.toString( )); } } This example creates the test class XMLElementTest to test the class XMLElement . The test method testEmptyElement( ) creates an XMLElement named test and uses the assertXMLEqual( ) test assert method to verify its contents. Since the assert method may throw an Exception , the test method declaration also states that an Exception may be thrown. If the Exception is thrown, the unit test framework will catch the Exception and indicate that the test resulted in an error. The tested class XMLElement is given in Example 10-2. It represents an XML element. Example 10-2. The class XMLElement XMLElement.java public class XMLElement { private String name; XMLElement (String n) { name = n; } public String toString ( ) { return "<"+name+"/>"; } } This initial version of XMLElement only knows how to represent an empty element. Its toString( ) method returns the XML string <test/> . Since a ssertXMLEquals( ) tests for syntactical equivalence, not literal string equivalence, comparing this string to the expected value <test></test> succeeds. Both strings are valid XML representations of an empty element named test. A test can also verify that two XML strings are literally identical. All XMLUnit comparison tests use the class Diff to compare XML strings. The assertXMLEquals() method creates an instance of Diff and calls its method similar() to check if the two strings are equivalent. To test for identical XML strings, a Diff is created and passed to assertXMLIdentical( ) , as shown in Example 10-3. Example 10-3. Testing for identical XML content XMLElementTest.java public void testEmptyElementIdentical ( ) throws Exception { XMLElement element = new XMLElement("test"); String expected = "<test/>"; Diff diff = new Diff(expected, element.toString( )); assertXMLIdentical (diff, true); } The second argument to assertXMLIdentical() , the Boolean TRUE , indicates that the test should pass if the XML strings are identical. Passing FALSE indicates that the test should pass if the strings are not identical. An XML element can have both text content and child elements. Example 10-4 demonstrates unit tests for adding contents and children to XMLElement . Example 10-4. Test adding content and children to XMLElement XMLElementTest.java public void testContent ( ) throws Exception { XMLElement element = new XMLElement("test", "content"); String expected = "<test>content</test>"; assertXMLEqual (expected, element.toString( )); } public void testAddChildren ( ) throws Exception { XMLElement element = new XMLElement("test"); XMLElement child = new XMLElement("child", "content"); XMLElement child2 = new XMLElement("child2", "content2"); element. addChild ( child ); element. addChild ( child2 ); String expected = "<test><child>content</child><child2>content2</child2></test>"; assertXMLEqual (expected, element.toString( )); } Again, assertXMLEqual( ) is used to verify the XML produced by XMLElement . The new behaviors being tested are the generation of XML for an element with text content and for an element with two children, each with their own text content. Example 10-5 implements XMLElement to support adding text content and children to an element. Example 10-5. Version of XMLElement supporting text content and child elements XMLElement.java import java.util.*; public class XMLElement { private String name; private String content; private Vector children; XMLElement (String n) { name = n; content = ""; children = new Vector( ); } XMLElement (String n, String c) { name = n; content = c; children = new Vector( ); } public void addChild (XMLElement child) { children.addElement( child ); } public String toString ( ) { if ( content.length( ) == 0 && children.size( ) == 0 ) return "<"+name+"/>"; else { String result = "<"+name+">"+content; for (Enumeration e = children.elements( ); e.hasMoreElements( ); ) { XMLElement element = (XMLElement)e.nextElement( ); result += element.toString( ); } result += "</"+name+">"; return result; } } } A new XMLElement constructor allows content to be specified when an element is created. The method addChild( ) allows child elements to be added. Testing individual XML elements is relatively easy. When it is necessary to unit test entire XML documents, XMLUnit really shows its usefulness . It supports building tests that compare and validate documents, as well as extracting and validating a document's nodes. With this test support, the task of building a custom XML document format becomes well-suited to test driven development methods . It makes sense to first test and build the functionality to write an empty document and then, as elements and attributes are added to the document format, to write additional tests for them. The design of an XML document format is codified in a Document Type Definition (DTD). A DTD contains the specification for a particular document type in terms of both the elements it may contain and the contents of those elements. Elements may contain attributes, text contents, and child elements. An XML document may contain its own DTD as part of its header information, or it may contain a reference to another file where the DTD is found. It is a matter of personal choice whether you write unit tests that specifically test the contents of the DTD. Regardless of whether the DTD itself is tested, its validity is indirectly verified by unit tests that check the validity of XML documents that use it. The most basic XMLUnit test to verify a document is assertXMLValid() . It takes an XML document represented as a string, parses it, and fails if there are any errors. Example 10-6 demonstrates validation of an XML document using this assertion. Example 10-6. Test using assertXMLValid to validate an XML document LibraryXMLDocTest.java import org.custommonkey.xmlunit.*; public class LibraryXMLDocTest extends XMLTestCase { public void testValid ( ) throws Exception { Library library = new Library( ); LibraryXMLDoc doc = new LibraryXMLDoc( library ); assertXMLValid ( doc.toString( ) ); } } This test creates an instance of the new class LibraryXMLDoc and tests the validity of the XML document created by its toString( ) method. To pass the assertXMLValid( ) test, an XML document must contain a DOCTYPE definition, a DTD defining an element type, and a root element. Example 10-7 shows the simplest implementation of LibraryXMLDoc that will pass this test. Example 10-7. Simple version of LibraryXMLDoc to produce a valid XML document LibraryXMLDoc.java public class LibraryXMLDoc { public String toString ( ) { return "<!DOCTYPE library [" + "<!ELEMENT library (#PCDATA) >]>" + "<library/>"; } } The XML document produced contains a DTD specifying the DOCTYPE library and an element type also named library . It contains one empty library element as well. Since a Library contains Book s, the LibraryXMLDoc DTD logically specifies a root library element containing book elements. A book element has child title and author elements. Example 10-8 shows the DTD for the library XML document implemented by LibraryXMLDoc . Example 10-8. The library document DTD <!DOCTYPE library [ <!ELEMENT library (book*) > <!ELEMENT book (title,author) > <!ELEMENT title (#PCDATA) > <!ELEMENT author (#PCDATA) > ]> This DTD specifies a document format in which a library element contains zero or more book elements, a book contains title and author elements, and the title and author elements contain character data. Example 10-9 shows a unit test for an XML document that is compliant with this DTD. The document should contain a library element, which contains a book element. This in turn contains title and author elements. At this point, it also makes sense to refactor LibraryXMLDocTest into a fixture to reduce code duplication between tests. Example 10-9. Testing the library, book, title, and author elements LibraryXMLDocTest.java import java.io.*; import org.w3c.dom.*; import javax.xml.parsers.*; import org.xml.sax.InputSource; import org.custommonkey.xmlunit.*; public class LibraryXMLDocTest extends XMLTestCase { private Library library; private DocumentBuilder builder; public void setUp ( ) throws Exception { library = new Library( ); DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance( ); builder = builderFactory.newDocumentBuilder( ); } public void testReadDocOneBook ( ) throws Exception { library.addBook(new Book("On the Road", "Jack Kerouac")); LibraryXMLDoc doc = new LibraryXMLDoc( library ); InputSource in = new InputSource(new StringReader( doc.toString( ) )); Document response = builder.parse( in ); NodeList books = response.getElementsByTagName("book"); assertEquals(1, books.getLength( )); Node bookNode = books.item(0); Node titleNode = bookNode.getFirstChild( ); Text titleText = (Text)titleNode.getFirstChild( ); Node authorNode = bookNode.getLastChild( ); Text authorText = (Text)authorNode.getFirstChild( ); assertEquals ("On the Road", titleText.getData( )); assertEquals ("Jack Kerouac", authorText.getData( )); } } The test method testReadDocOneBook( ) relies on several external objects to parse the XML document. These include a javax.xml.parsers.DocumentBuilder parser and Document , NodeList , Node , and Text objects to contain the parsed XML entities. Using these constructs, the test obtains the book , title , and author elements, and verifies the title and author text contents. For the implementation of LibraryXMLDoc to pass these tests, see the end of this section. The previous example parses a document and extracts individual node values. To avoid tedious repetitions of this kind of code, XMLUnit offers support for automatically walking the XML node tree with the NodeTest class and the NodeTester interface. Using this feature requires using an XML implementation that supports the DocumentTraversal interface, such as the Xerces parser. Example 10-10 shows a unit test that uses NodeTest and NodeTester to walk the tree and check the element names and text content values. Example 10-10. Walking the tree to test node values LibraryXMLDocTest.java public void testWalkTree ( ) throws Exception { XMLUnit.setControlParser( "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl"); library.addBook(new Book("title1", "author1")); library.addBook(new Book("title2", "author2")); LibraryXMLDoc doc3 = new LibraryXMLDoc( library ); String testDoc = doc3.toString( ); NodeTest nodeTest = new NodeTest(testDoc); assertNodeTestPasses (nodeTest, new LibraryNodeTester ( ), new short[] {Node.TEXT_NODE, Node.ELEMENT_NODE}, true); } private class LibraryNodeTester extends AbstractNodeTester { private String currName = ""; public void testText (Text text) throws NodeTestException { String txt = text.getData( ); System.out.println("text="+txt); if ((currName.equals("title") && txt.substring(0,5).equals("title")) (currName.equals("author") && txt.substring(0,6).equals("author"))) return; throw new NodeTestException("Incorrect text value", text); } public void testElement (Element element) throws NodeTestException { String name = element.getLocalName( ); System.out.println("name="+name); if (!name.equals("library") && !name.equals("book") && !name.equals("title") && !name.equals("author")) throw new NodeTestException("Unexpected name", element); if (name.equals("title") name.equals("author")) currName = name; } public void noMoreNodes (NodeTest nodeTest) throws NodeTestException {} } The test method is named testWalkTree( ) . It first calls setControlParser( ) to use the Xerces parser. Next , it adds two Book s to the Library and creates a LibraryXMLDoc . An instance of NodeTest is created with the XML document string as its argument. The assert method assertNodeTestPasses() is called. This method takes an AbstractNodeTester as an argument, along with an array of Node types telling it which XML nodes to test. The rest of the example is a custom AbstractNodeTester class named LibraryNodeTester that performs the actual testing of Node values. The method testElement( ) receives Element objects and the method testText( ) receives Text objects. In this example, the Elements are tested to verify they are either named library , book , title , or author , and the Text objects are tested to verify they contain the string title or author . The following output from running the test demonstrates how it works. The NodeTest object traverses the document tree and passes all of the elements and text contents to LibraryNodeTester : $ java junit.textui.TestRunner LibraryXMLDocTest .name=library name=book name=title text=title2 name=author text=author2 name=book name=title text=title1 name=author text=author1 A useful addition to the XML specification is the XML Path Language , known as XPath. It allows XML documents to be queried and manipulated using a URL-like path notation. XMLUnit offers a number of test assert methods that verify the results of XPath statements. Example 10-11 shows a unit test that creates XPath expressions to find book elements in a library XML document, and compares their results. Example 10-11. Testing XPath expressions LibraryXMLDocTest.java public void testXpath( ) throws Exception { library.addBook(new Book("On the Road", "Jack Kerouac")); library.addBook(new Book("Dune", "Frank Herbert")); LibraryXMLDoc doc = new LibraryXMLDoc( library ); String xmlTest = doc.toString( ); assertXpathExists ("//book[title='Dune']", xmlTest); assertXpathExists ("//book[author='Jack Kerouac']", xmlTest); assertXpathNotExists ("//book[author='Nobody']", xmlTest); assertXpathsEqual ("//book[title='Dune']", "//book[author='Frank Herbert']", xmlTest); assertXpathsNotEqual ("//book[title='Dune']", "//book[title='On the Road']", xmlTest); } This test creates XPath expressions to query book elements by title and author, and verifies that the books are found using assertXpathExists() . It also verifies whether two XPath expressions return the same element using the assert methods assertXpathsEqual( ) and assertXpathsNotEqual() . For more details on XPath, see the official W3C XPath Recommendation at http://www.w3.org/TR/xpath. |