XmlUnit Tutorial with Examples

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 !!

Sourcecode on Github

Comments

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode