2

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>johndoe@email.com</Email>
</Person>
<Person>
<FirstName>Mickey</FirstName>
<LastName>Mouse</LastName>
<Email>mm@email.com</Email>
</Person>
<Person>
<FirstName>John</FirstName>
<LastName>Doe</LastName>
<Email />
</Person>
</Parent>

Test

<Parent>
<Person>
<FirstName>Mickey</FirstName>
<LastName>Mouse</LastName>
<Email>mm@email.com</Email>
</Person>
<Person>
<FirstName>John</FirstName>
<LastName>Doe</LastName>
<Email>johndoe@email.com</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();
Kerruba
  • 1,779
  • 1
  • 17
  • 25
  • which alpha of XMLUnit 2 are you using? It may be the implementation of conditionalBuilder prior to alpha3 - https://github.com/xmlunit/xmlunit/issues/40 – Stefan Bodewig Jan 22 '16 at 14:21
  • I was using alpha2, I'm now trying with alpha3 – Kerruba Jan 22 '16 at 15:26
  • [Update] Same problem with the alpha3, seems not able to pick the correct "control" element. – Kerruba Jan 22 '16 at 16:41
  • ah, overlooked your definition of `DefaultNodeMatcher`. Please use the varargs constructor rather than `or` - i.e. `new DefaultNodeMatcher(selector, ElementSelectors.Default)`. – Stefan Bodewig Jan 22 '16 at 20:06
  • I used the varargs constructor and I was able to pair correctly the nodes, still I get this error that I don't understand (the xpath is not important): Expected child nodelist sequence '8' but was '12' - comparing at /BioSampleGroup[1]/Person[1] to at /BioSampleGroup[1]/Person[5]...what does this mean? – Kerruba Jan 23 '16 at 11:54
  • By default elements that are in different order result in a "SIMILAR" comparison outcome. Your elements *are* in a different order and XMLUnit tells you so. If you don't care you can either be content with testing for "similar" or add a `DifferenceEvaluator` that downgrades this specific difference to "EQUAL". – Stefan Bodewig Jan 23 '16 at 13:45
  • So something like this should be fine or there's a better way of write it? //other code .withDifferenceEvaluator(((comparison, outcome) -> { if (outcome == ComparisonResult.DIFFERENT && comparison.getType() == ComparisonType.CHILD_NODELIST_SEQUENCE) { return ComparisonResult.EQUAL; } return outcome; })) //other code – Kerruba Jan 23 '16 at 15:27
  • you should probably `chain` your implementation with `DifferenceListeners.Default` and drop the check of outcome. If this works for you, we should turn this into an answer, StackOverflow doesn't like long discussions in comments ;-) – Stefan Bodewig Jan 23 '16 at 16:20
  • Yep, we can do that. Thanks :-) – Kerruba Jan 23 '16 at 16:28
  • I suggest you write the answer yourself and accept it. – Stefan Bodewig Jan 23 '16 at 17:07

1 Answers1

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

Community
  • 1
  • 1
Kerruba
  • 1,779
  • 1
  • 17
  • 25