Skip to content

DifferenceEvaluator

Varshaav16 edited this page Nov 19, 2022 · 7 revisions

DifferenceEvaluator

A DifferenceEvaluator decides about the outcome of a comparison. A couple of convenience implementations have been collected in the DifferenceEvaluators class.

Default DifferenceEvaluator

DifferenceEvaluators.Default uses rules very similar to those of XMLUnit for Java 1.x in order to decide whether a technically different outcome is ComparisonResult.DIFFERENT or rather a not so critical difference that would still allow the pieces of XML to be considered SIMILAR.

With the default evaluator the following differences are handled as similar:

  • CDATA and Text nodes with the same content
  • DOCTYPE differences
  • different xsi:schemaLocation and xsi:noNamspaceSchemaLocation
  • different XML namespaces prefixes
  • explicit/implicit status of attributes.
  • a different order of child nodes
  • XML encoding

If you use your own DifferenceEvaluator implementation you can combine it with the default DifferenceEvaluator.

Note
The order of child nodes is consider as SIMILAR provided the NodeMatcher selects the correct nodes for comparison. As the DefaultDifferenceEvaluator uses ElementSelectors.Default for selecting the nodes.

Example implementations

❗ This Example implementations are strongly simplified without exception handling or not-null checks. They should only be a clue what a DifferenceEvaluator can do. ❗

Ignoring a special attribute

In some cases you want ignore some special differences of an XML content. Typical cases are elements/attributes which are filled with random values like UUIDs or current date/time information.

class IgnoreAttributeDifferenceEvaluator implements DifferenceEvaluator {

    private String attributeName;

    public IgnoreAttributeDifferenceEvaluator(String attributeName) {
        this.attributeName = attributeName;
    }

    @Override
    public ComparisonResult evaluate(Comparison comparison, ComparisonResult outcome) {
        if (outcome == ComparisonResult.EQUAL) return outcome; // only evaluate differences.
        final Node controlNode = comparison.getControlDetails().getTarget();
        if (controlNode instanceof Attr) {
            Attr attr = (Attr) controlNode;
            if (attr.getName().equals(attributeName)) {
                return ComparisonResult.SIMILAR; // will evaluate this difference as similar
            }
        }
        return outcome;
    }
} 

Usage:

final String control = "<a><b attr=\"abc\"></b></a>";
final String test = "<a><b attr=\"xyz\"></b></a>";

Diff myDiff = DiffBuilder.compare(control).withTest(test)
        .withDifferenceEvaluator(new IgnoreAttributeDifferenceEvaluator("attr"))
        .checkForSimilar()
        .build();

Assert.assertFalse(myDiff.toString(), myDiff.hasDifferences());

for Java or for .NET:

public class IgnoreAttributeDifferenceEvaluator {
    private string attributeName;

    public IgnoreAttributeDifferenceEvaluator(string attributeName) {
        this.attributeName = attributeName;
    }

    public ComparisonResult Evaluate(Comparison comparison, ComparisonResult outcome) {
        if (outcome == ComparisonResult.EQUAL) return outcome; // only evaluate differences.
        XmlNode controlNode = comparison.ControlDetails.Target;
        XmlAttribute attr = controlNode as XmlAttribute;
        if (attr != null) {
            if (attr.Name == attributeName) {
                return ComparisonResult.SIMILAR; // will evaluate this difference as similar
            }
        }
        return outcome;
    }
}

usage

string control = "<a><b attr=\"abc\"></b></a>";
string test = "<a><b attr=\"xyz\"></b></a>";

var myDiff = DiffBuilder.Compare(control).WithTest(test)
    .WithDifferenceEvaluator(new IgnoreAttributeDifferenceEvaluator("attr").Evaluate)
    .CheckForSimilar()
    .Build();

Assert.IsFalse(myDiff.HasDifferences(), myDiff.ToString());

Compare an element manually

In some cases you don't want compare the content of an element as String. This example shows how to compare a special element with BigDecimal.
Because for our fictive business case it doesn't mater if the value is "1.0" or "1.00".

class BigDecimalElementDifferenceEvaluator implements DifferenceEvaluator {

    private String elementName;

    public BigDecimalElementDifferenceEvaluator(String elementName) {
        this.elementName = elementName;
    }

    @Override
    public ComparisonResult evaluate(Comparison comparison, ComparisonResult outcome) {
        if (outcome == ComparisonResult.EQUAL) return outcome; // only evaluate differences.
        final Node controlNode = comparison.getControlDetails().getTarget();
        final Node testNode = comparison.getTestDetails().getTarget();
        if (controlNode.getParentNode() instanceof Element && testNode.getParentNode() instanceof Element) {
            Element controlElement = (Element) controlNode.getParentNode();
            Element testElement = (Element) testNode.getParentNode();
            if (controlElement.getNodeName().equals(elementName)) {
                final String controlValue = controlElement.getTextContent();
                final String testValue = testElement.getTextContent();
                if (new BigDecimal(controlValue).compareTo(new BigDecimal(testValue)) == 0) {
                    return ComparisonResult.SIMILAR;
                }
            }
        }
        return outcome;
    }
}

Usage:

final String control = "<a><amount>1</amount></a>";
final String test = "<a><amount>1.0000</amount></a>";

Diff myDiff = DiffBuilder.compare(control).withTest(test)
        .withDifferenceEvaluator(new BigDecimalElementDifferenceEvaluator("amount"))
        .checkForSimilar()
        .build();

Assert.assertFalse(myDiff.toString(), myDiff.hasDifferences());

combine DifferenceEvaluators

If you want use your own DifferenceEvaluator implementation in combination with the default DifferenceEvaluator you must combine them:

Diff myDiff = DiffBuilder.compare(control).withTest(test)
        .withDifferenceEvaluator(
            DifferenceEvaluators.chain(DifferenceEvaluators.Default,
            new MyCustomDifferenceEvaluator()))
        .checkForSimilar()
        .build();

There are two helper methods to combine DifferenceEvaluators:

  • DifferenceEvaluators.first(...)
    Combines multiple DifferenceEvaluators so that the first one that changes the outcome wins.
  • DifferenceEvaluators.chain(...)
    Combines multiple DifferenceEvaluators so that the result of the first Evaluator will be passed to the next Evaluator.