Learn to use XmlUnit 2.x to test and verify XML documents with easy-to-understand examples. XmlUnit requires us to know precisely the XML content we want to verify comparing against a given test XML.
For assertions, we are using Hamcrest matchers library.
1. Maven Dependency
Include the latest version of xmlunit-core and xmlunit-matchers dependencies into the project.
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-matchers</artifactId>
<version>2.9.0</version>
</dependency>
<!-- If using Hamcrest matchers -->
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>2.2</version>
</dependency>
2. Reading and Comparing XML Sources
2.1. Reading XML Sources
For making comparisons, XmlUnit‘s CompareMatcher
can take inputs from various sources such as:
java.lang.String
java.io.File
java.io.InputStream
java.io.Reader
byte array
org.w3c.dom.Document
etc.
The following code demonstrates to read a file widget.xml using different ways.
@Test
void createSource_ThenSuccess() throws ParserConfigurationException, IOException, SAXException {
//1 - Using File
Input.from(new File("widget.xml"));
Input.fromFile("widget.xml");
Input.fromFile(getClass().getClassLoader().getResource("widget.xml").getPath());
//2 - Using String
Input.from("<widget><id>1</id><name>demo</name></widget>");
Input.fromString("<widget><id>1</id><name>demo</name></widget>");
//3 - Using Stream
Input.from(XmlUnitExamples.class.getResourceAsStream("widget.xml"));
Input.fromStream(XmlUnitExamples.class.getResourceAsStream("widget.xml"));
//4 - From Reader
Input.from(new StringReader("<widget><id>1</id><name>demo</name></widget>"));
Input.fromReader(new StringReader("<widget><id>1</id><name>demo</name></widget>"));
//5 - From Byte Array
Input.from("<widget><id>1</id><name>demo</name></widget>".getBytes());
Input.fromByteArray("<widget><id>1</id><name>demo</name></widget>".getBytes());
//6 - From Document
DocumentBuilder b = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document document = b.parse(new File("widget.xml"));
Input.from(document);
Input.fromDocument(document);
}
2.2. Comparing XML Sources
We can compare two XMLs in various ways. In the following example, we are comparing two XML strings.
String testXml = "<widget><id>1</id><name>demo</name></widget>";
String controlXml = "<widget><id>1</id><name>demo</name></widget>";
assertThat(testXml, isIdenticalTo(identicalXml));
Similarily, in the next example, we are comparing two XML files.
assertThat(new File("widget.xml"), isIdenticalTo(identicalXml));
assertThat(new File("widget.xml"), isIdenticalTo(new File("otherFile.xml")));
We can mix different types of sources in comparisons.
assertThat(new File("widget.xml"), isIdenticalTo(Input.from(identicalXml)));
3. Comparison Examples using XmlUnit
Let us check out different ways to compare two XMLs using XmlUnit.
3.1. Comparing Identical XMLs
Two XMLs are considered identical when they have the exact same content i.e. both XMLs have the same tags, in the same order and with the same values.
The following example uses CompareMatcher‘s static methods to compare two XMLs.
@Test
void compareIdenticalAndSimilarXmlWithHamcrest_ThenSuccess() throws Exception {
String testXml = "<widget><id>1</id><name>demo</name></widget>";
String identicalXml = "<widget><id>1</id><name>demo</name></widget>";
String nonIdenticalXml = "<widget><name>demo</name><id>1</id></widget>";
assertThat(testXml, isIdenticalTo(identicalXml));
assertThat(testXml, not(isIdenticalTo(nonIdenticalXml)));
}
We can also DiffBuilder
API that collects all the differences between two XMLs. DiffBuilder is a fluent API to get the differences in a more controlled manner.
@Test
void compareIdenticalAndSimilarXmlDiff_ThenSuccess() throws Exception {
String testXml = "<widget><id>1</id><name>demo</name></widget>";
String identicalXml = "<widget><id>1</id><name>demo</name></widget>";
Diff diffForIdentical = DiffBuilder
.compare(testXml)
.withTest(identicalXml)
.checkForIdentical()
.build();
assertThat(IterableUtils.size(diffForIdentical.getDifferences()), equalTo(0));
}
3.2. Comparing Similar XMLs
Two XMLs are considered similar when they have the same tags and values, but the order of the tags is different. In the following example, nonIdenticalXml has similar tags, but the order of id and name are different.
By default, XmlUnit uses DefaultNodeMatcher which matches the XML tags at the same depth level from the root tag and in the same order. To ask XmlUnit to ignore the order, we use ElementSelectors.byName which sorts all the tags by name in the same depth before starting to match them.
@Test
void compareIdenticalAndSimilarXmlWithHamcrest_ThenSuccess() throws Exception {
String testXml = "<widget><id>1</id><name>demo</name></widget>";
String nonIdenticalXml = "<widget><name>demo</name><id>1</id></widget>";
assertThat(testXml, isSimilarTo(nonIdenticalXml)
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byName)));
}
Similarily, we can use the ElementSelectors.byName with DiffBuilder API.
@Test
void compareIdenticalAndSimilarXmlDiff_ThenSuccess() throws Exception {
String testXml = "<widget><id>1</id><name>demo</name></widget>";
String nonIdenticalXml = "<widget><name>demo</name><id>1</id></widget>";
Diff diffForSimilarity = DiffBuilder
.compare(testXml)
.withTest(nonIdenticalXml)
.checkForSimilar()
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byName))
.build();
assertThat(IterableUtils.size(diffForSimilarity.getDifferences()), equalTo(0));
}
3.3. Comparing or Ignoring Specific Tags
There may be a requirement to ignore specific tags or compare only a few specific tags while matching the XMLs. We can acheive it using the withNodeFilter() method. This method will be invoked for each node in XML. If the method returns true then the comparison will happen, else skipped.
In following XMLs, only id tag will be compared. The name tag will be skipped. We can include or exclude any number of tags by creating a complex expression inside the withNodeFilter() method.
@Test
void compareOnlySpecificTags_ThenSuccess() {
String testXml = "<widget><id>1</id><name>test</name></widget>";
String otherXml = "<widget><name>live</name><id>1</id></widget>";
Diff diffs = DiffBuilder.compare(testXml)
.withTest(otherXml)
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byName))
.withNodeFilter(
node -> node.getNodeName().equalsIgnoreCase("id")
)
.build();
assertThat(IterableUtils.size(diffs.getDifferences()), equalTo(0));
}
3.4. Comparing using XPaths
Comparing the whole XML is not feasible in all situations. We can use XPath expressions if we want to compare only a specific section of XML.
@Test
void containsAsChildXmlUsingXPath_ThenSuccess() throws Exception {
String fullXml = "<widget><id>1</id><name>demo</name></widget>";
//Contains 'id' tag
assertThat(fullXml, HasXPathMatcher.hasXPath("/widget/id"));
//Compare value of 'id' tag
assertThat(fullXml, EvaluateXPathMatcher.hasXPath("/widget/id/text()", equalTo("1")));
}
4. Normalizing XMLs
The XML received may not always be normalized and clean. We can normalize the XMLs using two approaches:
4.1. Using Source Decorators
XmlUnit provides following decorators that we can use with Source instance build in the first section.
CommentLessSource
: provides XML that consists of the original source’s content with all comments removed.WhitespaceStrippedSource
: removes all empty text nodes and trims the remaining text nodes. Effectively, it removed all the empty strings from the XML, including the node content.WhitespaceNormalizedSource
: replaces all whitespace characters found in Text nodes with Space characters and collapses consecutive whitespace characters into a single Space.NormalizedSource
: adjacent text nodes are merged to single nodes and empty Text nodes removed (recursively).ElementContentWhitespaceStrippedSource
: removes all text nodes solely consisting of whitespace. It removes all “element content whitespace”, i.e. text content between XML elements that is just an artifact of “pretty printing” XML.
@Test
void normalizedXmlSources_ThenSuccess() throws Exception {
CommentLessSource commentLessSource
= new CommentLessSource(Input.fromFile("widget.xml").build());
WhitespaceStrippedSource whitespaceStrippedSource
= new WhitespaceStrippedSource(Input.fromFile("widget.xml").build());
WhitespaceNormalizedSource whitespaceNormalizedSource
= new WhitespaceNormalizedSource(Input.fromFile("widget.xml").build());
ElementContentWhitespaceStrippedSource elementContentWhitespaceStrippedSource
= new ElementContentWhitespaceStrippedSource(Input.fromFile("widget.xml").build());
}
4.2. Using CompareMatcher
Another way to normalize XMLs is using the factory methods provided by CompareMatcher class while comparing both XML contents.
@Test
void normalizedXmlMatchers_ThenSuccess() throws Exception {
String testXml = "<widget><id>1</id><name>demo</name></widget>";
String identicalXml = "<widget><id>1</id><name>demo</name></widget>";
assertThat(testXml, isIdenticalTo(identicalXml).normalizeWhitespace());
assertThat(testXml, isIdenticalTo(identicalXml).ignoreComments());
}
5. Conclusion
In this XmlUnit 2.x tutorial, we learned to create different types of XML sources, normalizing with decorators and comparing for identical, similar and fragments using XPath.
Happy Learning !!
Leave a Reply