XMLUnit 2.0 - Unable to avoid order comparison with custom Element selector

3.1k views Asked by At

first of all thank you for this wonderful library, it's really great.

I'm having a problem comparing elements in different order within my xml document. I've developed a custom ElementSelector to use with the NodeMatcher (later the code) but still it seems to check based on element order more than element content. Let me write an example

Control

<Parent>
<Person>
<FirstName>John</FirstName>
<LastName>Doe</LastName>
<Email>[email protected]</Email>
</Person>
<Person>
<FirstName>Mickey</FirstName>
<LastName>Mouse</LastName>
<Email>[email protected]</Email>
</Person>
<Person>
<FirstName>John</FirstName>
<LastName>Doe</LastName>
<Email />
</Person>
</Parent>

Test

<Parent>
<Person>
<FirstName>Mickey</FirstName>
<LastName>Mouse</LastName>
<Email>[email protected]</Email>
</Person>
<Person>
<FirstName>John</FirstName>
<LastName>Doe</LastName>
<Email>[email protected]</Email>
</Person>
<Person>
<FirstName>John</FirstName>
<LastName>Doe</LastName>
<Email />
</Person>
</Parent>

How I made the Diff

Diff diff = DiffBuilder.compare(refSource)
                        .withTest(testSource)
                        .checkForSimilar()
                        .ignoreWhitespace()
                        .normalizeWhitespace()
                        .withNodeMatcher(new DefaultNodeMatcher(selector))
                        .build();

How I created the ElementSelector selector

ElementSelector selector = ElementSelectors.conditionalBuilder()
                        .whenElementIsNamed("Person").thenUse(new PersonNodeMatcher())
                        .defaultTo(ElementSelectors.byNameAndText).build();

How is actually implemented the PersonNodeMatcher

public class PersonNodeMatcher extends BaseElementSelector {

@Override
protected boolean canBeCompared(Element control, Element test) {

    String controlFirstName = control.getElementsByTagName("FirstName").item(0).getTextContent();
    String controlLastName  = control.getElementsByTagName("LastName").item(0).getTextContent();

    Node controlEmailNode = control.getElementsByTagName("Email").item(0);
    String controlEmail = null;
    if ( controlEmailNode != null) {
        controlEmail = controlEmailNode.getTextContent();
    }


    String testFirstName = test.getElementsByTagName("FirstName").item(0).getTextContent();
    String testLastName  = test.getElementsByTagName("LastName").item(0).getTextContent();


    Node testEmailNode = test.getElementsByTagName("Email").item(0);
    String testEmail = null;
    if (testEmailNode != null) {
        testEmail = testEmailNode.getTextContent();
    }

    return bothNullOrEqual(controlFirstName,testFirstName) &&
              bothNullOrEqual(controlLastName,testLastName) &&
              bothNullOrEqual(controlEmail,testEmail);

}

The routine is still checking the nodes in order, so they will never match. I thought that providing a node custom node matcher I would be able to check all the Element with the tagName provided.

Am I doing something wrong or simply is not possible?

[UPDATE] Using the alpha3 I had to do some modification to the code, specifically:

ElementSelector selector = ElementSelectors.conditionalBuilder()
                        .whenElementIsNamed("Person").thenUse(new PersonNodeMatcher()).build();


    Diff diff = DiffBuilder.compare(refSource)
                        .withTest(testSource)
                        .checkForSimilar()
                        .ignoreWhitespace()
                        .normalizeWhitespace()
                        .withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.or(selector,ElementSelectors.Default)))
                        .build();
1

There are 1 answers

0
Kerruba On BEST ANSWER
  1. I moved to the most recent alpha, the alpha-03. There was an issue with the alpha-02;
  2. Instead of using the ElementSelectors.or inside of the DefaultNodeMatcher, I used the varargs constructor

    Diff diff = DiffBuilder.compare(refSource)
                           .withTest(testSource)
                           .checkForSimilar()
                           .ignoreWhitespace()
                           .normalizeWhitespace()
                           .withNodeMatcher(
                                 new DefaultNodeMatcher(
                                        selector,ElementSelectors.Default)
                           )
                           .build();
    

    The difference between the two approaches is explained here.

  3. This solved my primary issue, still there was a problem since the DifferenceEvaluator was outputting that document were different, due to this (look at the end of paragraph). Indeed, the document are SIMILAR and not IDENTICAL, since the order of the inner elements is not equal. In order to prevent such an output from the DifferenceEvaluator, at the moment I've updated the DiffBuilder with a specific DifferenceEvaluator

    .withDifferenceEvaluator(((comparison, outcome) -> {
        if (outcome == ComparisonResult.DIFFERENT && 
            comparison.getType() == ComparisonType.CHILD_NODELIST_SEQUENCE) {
               return ComparisonResult.EQUAL;
        }
    
        return outcome;
    }))
    

    even if probably the best solution, as suggested by Stefan Bodewig, would be to chain my implementation with DifferenceListeners.Default and drop the check of outcome.