XMLUnit nested element testing

498 views Asked by At

I have two XML files that I want to compare. I am using XMLUnit 2. But elements can be out of order (including sub element ordering) All three examples below need to be considered equivalent:

<Product>
    <Property>
        <Container value="1">Test 01</Container>
        <Container value="3">Test 02</Container>
        <Container value="5">Test 03</Container>
    </Property>
    <Property>
        <Container value="2">Test A</Container>
        <Container value="4">Test B</Container>
        <Container value="6">Test C</Container>
    </Property>
</Product>

<Product>
    <Property>
        <Container value="5">Test 03</Container>
        <Container value="1">Test 01</Container>
        <Container value="3">Test 02</Container>
    </Property>
    <Property>
        <Container value="2">Test A</Container>
        <Container value="4">Test B</Container>
        <Container value="6">Test C</Container>
    </Property>

<Product>
    <Property>
        <Container value="2">Test A</Container>
        <Container value="4">Test B</Container>
        <Container value="6">Test C</Container>
    </Property>
    <Property>
        <Container value="5">Test 03</Container>
        <Container value="1">Test 01</Container>
        <Container value="3">Test 02</Container>
    </Property>
</Product>

It seems like XMLUnit should be able to do this fairly easily but I don't know what I need to do to configure the test. I should add that nesting can get deeper where container has more elements under it that demonstrate the same issue.

---

POSSIBLE SOLUTION:

public class SubNodeEqualityElementSelector implements ElementSelector {

    public SubNodeEqualityElementSelector() {

    }

    @Override
    public boolean canBeCompared(Element controlElement, Element testElement) {
        // test input nodes for equality first.
        if (!ElementSelectors.byNameAndAllAttributes.canBeCompared(controlElement, testElement)) {
            return false;
        }
        if (!ElementSelectors.byNameAndText.canBeCompared(controlElement, testElement)) {
            return false;
        }

        // test children for equality.  For each ctrl child, make sure that a matching element is found in the test set.
        ArrayList<Element> ctrlChildren = getChildElements(controlElement);
        ArrayList<Element> testChildren = getChildElements(testElement);
        ArrayList<Element> ctrlChildrenResults = new ArrayList<Element>(ctrlChildren);
        ArrayList<Element> testChildrenResults = new ArrayList<Element>(testChildren);

        for (Element ctrlChild : ctrlChildren) {
            for (Element testChild : testChildren) {
                if (ElementSelectors.byNameAndAllAttributes.canBeCompared(ctrlChild, testChild) &&
                        ElementSelectors.byNameAndText.canBeCompared(ctrlChild, testChild)) {
                    ctrlChildrenResults.remove(ctrlChild);
                    testChildrenResults.remove(testChild);
                }
            }
        }

        return ctrlChildrenResults.size() == 0 && testChildrenResults.size() == 0;
    }

    private ArrayList<Element> getChildElements(Element elem) {
        ArrayList<Element> retVal = new ArrayList<Element>();

        NodeList childNodes = elem.getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node node = childNodes.item(i);
            if (node instanceof Element){
                Element element = (Element) node;
                retVal.add(element);
            }
        }

        return retVal;
    }

}

I tested the above class (SubNodeEqualityElementSelector) and it seems to work well!

1

There are 1 answers

2
Stefan Bodewig On BEST ANSWER

This is a pretty difficult example.

If you want to tackle this with XMLUnit 1.x then you'll have to code up an ElementQualifier that picks the correct Property element when faced with the list. You'll have to write it yourself, neither of the built-in versions will do. In addition you can only have one ElementQualifier so you'll also need to take care of the remaining comparisons, for example picking the correct Container - something that ElementNameAndAttributeQualifier could have done.

Using XMLUnit 2.x it doesn't get a lot easier, but at least you can use the conditional builder to combine ElementSelector. The user guide example in https://github.com/xmlunit/user-guide/wiki/SelectingNodes is close, it picks the correct "outer" element based on the nested text of the first "inner" element. In your case it looks as if you needed to pick the Property based on an attribute of the nested Containers, but unfortunately it's not enough to look at the first Container. I'm afraid we won't come up with an XPath that works so you probably still need to write Java code that picks the correct Property, but once you've got that, you can do something like

ElementSelectors.conditionalBuilder()
    .whenElementIsNamed("Property").thenUse(YOUR_ELEMENT_SELECTOR)
    .elseUse(ElementSelectors.byNameAndAllAttributes)
    .build();