XML Unit - Using custom element selectors on different xml elements
diff, java, xml, xmlunit, xmlunit-2
Solution
There is a subtle difference between using three `ElementSelector`s in the constructor of `DefaultNodeMatcher` and using a single `ElementSelector` with multiple conditions.
What you want to achieve with the three selectors could also be written as
ElementSelectors.conditionalBuilder()
.whenElementIsNamed("LANGUAGE")
.thenUse(ElementSelectors.byNameAndAllAttributes)
.whenElementIsNamed("CODE")
.thenUse(ElementSelectors.byNameAndText)
.elseUse(ElementSelectors.byName)
.build();
At first glance this seems to do the same, but it doesn't. In fact it will work. Here is what I get from your example:
Expected child nodelist length '4' but was '2' - comparing <LANGUAGES...> at /ROOT[1]/LANGUAGES[1] to <LANGUAGES...> at /ROOT[1]/LANGUAGES[1] (DIFFERENT)
Expected child 'LANGUAGE' but was 'null' - comparing <LANGUAGE...> at /ROOT[1]/LANGUAGES[1]/LANGUAGE[2] to <NULL> (DIFFERENT)
Expected child 'LANGUAGE' but was 'null' - comparing <LANGUAGE...> at /ROOT[1]/LANGUAGES[1]/LANGUAGE[3] to <NULL> (DIFFERENT)
Expected child nodelist length '4' but was '2' - comparing <CODES...> at /ROOT[1]/CODES[1] to <CODES...> at /ROOT[1]/CODES[1] (DIFFERENT)
Expected child 'CODE' but was 'null' - comparing <CODE...> at /ROOT[1]/CODES[1]/CODE[1] to <NULL> (DIFFERENT)
Expected child 'CODE' but was 'null' - comparing <CODE...> at /ROOT[1]/CODES[1]/CODE[3] to <NULL> (DIFFERENT)
Expected text value 'Max' but was 'Tim' - comparing <FIRSTNAME ...>Max</FIRSTNAME> at /ROOT[1]/CONTACT[1]/FIRSTNAME[1]/text()[1] to <FIRSTNAME ...>Tim</FIRSTNAME> at /ROOT[1]/CONTACT[1]/FIRSTNAME[1]/text()[1] (DIFFERENT)
So here is what happens: When XMLUnit looks at the `LANGUAGE` with `VALUE` `en` the first conditional `ElementSelector` you've built will return `false` for all candidate `LANGUAGE` elements.
When using the multi-arg `DefaultNodeMatcher` constructor the next `ElementSelector` will be asked - which will also return `false` in your case as it is only interested in `CODE` elements. And then the third `ElementSelector` is consulted which will happily accept any test element named `LANGUAGE`.
When using the single `ElementSelector` I've set up above the combined selector will never consult any of the alternatives as soon as a `Predicate` has returned `true`. This means there will be no second thoughts for the `LANGUAGE` element once the selector has returned `false`.
I'll try to update https://github.com/xmlunit/user-guide/wiki/SelectingNodes#nodematcher with your example and would love to get your help with improving the documentation.
Problem
I'm having a problem comparing various elements in my xml document with XMLUnit (2.2.1). In my document there are several xml elements and I want to know, whether they differ from each other. However, I don't want to compare all xml elements the same way. Sometimes I just want to compare them by their name.In other cases I want to compare them by name and attribute or name and text. Here is an example (see the comments) Control ``` <?xml version="1.0" encoding="UTF-8"?> <ROOT> <LANGUAGES> <!-- Compare LANGUAGE by Name and Attribute --> <LANGUAGE VALUE="DE" /> <LANGUAGE VALUE="EN" /> <LANGUAGE VALUE="IT" /> <LANGUAGE VALUE="FR" /> </LANGUAGES> <CODES> <!-- Compare CODE by Name and Text --> <CODE>10000-1</CODE> <CODE>20000-2</CODE> <CODE>30000-3</CODE> <CODE>40000-4</CODE> </CODES> <CONTACT> <!-- Compare CONTACT and Children just by Name --> <FIRSTNAME>Max</FIRSTNAME> <SURNAME>Mustermann</SURNAME> </CONTACT> </ROOT> ``` Test: ``` <?xml version="1.0" encoding="UTF-8"?> <ROOT> <LANGUAGES> <!-- Compare LANGUAGE by Name and Attribute --> <LANGUAGE VALUE="DE" /> <LANGUAGE VALUE="FR" /> </LANGUAGES> <CODES> <!-- Compare CODE by Name and Text --> <CODE>20000-2</CODE> <CODE>40000-4</CODE> </CODES> <CONTACT> <!-- Compare CONTACT and Children by Name --> <FIRSTNAME>Tim</FIRSTNAME> <SURNAME>Mustermann</SURNAME> </CONTACT> </ROOT> ``` I tried ElementSelectors in combination with ElementSelectors.conditionalBuilder (https://github.com/xmlunit/user-guide/wiki/SelectingNodes#conditional-elementselectors) to only apply an ElementSelector on a specific element (whenElementIsNamed). Maybe this isn't the right approach for what I want to achieve. This is my code I use for testing: ``` public void xmlDiff() { String control = getControlDocument(); // String test = getTestDocument(); // Diff myDiff = DiffBuilder.compare(control)// .withTest(test) // .ignoreWhitespace() // .ignoreComments() // .checkForSimilar() // .withNodeMatcher(new DefaultNodeMatcher(partialElementSelector("LANGUAGE", ElementSelectors.byNameAndAllAttributes), partialElementSelector("CODE",ElementSelectors.byNameAndText), ElementSelectors.byName)) // .build(); assertThat(myDiff.hasDifferences()).isTrue(); // } private ElementSelector partialElementSelector(final String expectedName, final ElementSelector elementSelector) { return ElementSelectors.conditionalBuilder().whenElementIsNamed(expectedName).thenUse(elementSelector).build(); } ``` What I actually need is the information, that two LANGUAGEs (EN,IT) and two CODEs (10000-1,30000-3) have been removed (not replaced) and FIRSTNAME changed. How can I get those information, with or without XML Unit (DiffBuilder)? Thank you for your help!