Skip to content

DifferenceEvaluator

Stefan Bodewig edited this page Mar 17, 2016 · 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.

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.
Clone this wiki locally