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