DiffBuilderTest.java
/*
This file is licensed to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.xmlunit.builder;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.xmlunit.util.Linqy.count;
import org.xmlunit.TestResources;
import org.xmlunit.XMLUnitException;
import org.xmlunit.diff.Comparison;
import org.xmlunit.diff.ComparisonControllers;
import org.xmlunit.diff.ComparisonFormatter;
import org.xmlunit.diff.ComparisonListener;
import org.xmlunit.diff.ComparisonResult;
import org.xmlunit.diff.ComparisonType;
import org.xmlunit.diff.Diff;
import org.xmlunit.diff.Difference;
import org.xmlunit.diff.DifferenceEvaluator;
import org.xmlunit.diff.DifferenceEvaluators;
import org.xmlunit.util.Predicate;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
public class DiffBuilderTest {
@Test
public void testDiff_withoutIgnoreWhitespaces_shouldFail() {
// prepare testData
String controlXml = "<a><b>Test Value</b></a>";
String testXml = "<a>\n <b>\n Test Value\n </b>\n</a>";
// run test
Diff myDiff = DiffBuilder.compare(Input.fromString(controlXml).build())
.withTest(Input.fromString(testXml).build())
.build();
// validate result
Assert.assertTrue(myDiff.toString(), myDiff.hasDifferences());
}
@Test
public void testDiff_withIgnoreWhitespaces_shouldSucceed() {
// prepare testData
String controlXml = "<a><b>Test Value</b></a>";
String testXml = "<a>\n <b>\n Test Value\n </b>\n</a>";
// run test
Diff myDiff = DiffBuilder.compare(Input.fromString(controlXml).build())
.withTest(Input.fromString(testXml).build())
.ignoreWhitespace()
.build();
// validate result
Assert.assertFalse("XML similar " + myDiff.toString(), myDiff.hasDifferences());
}
@Test
public void testDiff_withoutNormalizeWhitespaces_shouldFail() {
// prepare testData
String controlXml = "<a><b>Test Value</b></a>";
String testXml = "<a>\n <b>\n Test Value\n </b>\n</a>";
// run test
Diff myDiff = DiffBuilder.compare(Input.fromString(controlXml).build())
.withTest(Input.fromString(testXml).build())
.build();
// validate result
Assert.assertTrue(myDiff.toString(), myDiff.hasDifferences());
}
@Test
public void testDiff_withNormalizeWhitespaces_shouldSucceed() {
// prepare testData
String controlXml = "<a><b>Test Value</b></a>";
String testXml = "<a>\n <b>\n Test\n Value\n </b>\n</a>";
// run test
Diff myDiff = DiffBuilder.compare(Input.fromString(controlXml).build())
.withTest(Input.fromString(testXml).build())
.normalizeWhitespace()
.build();
// validate result
Assert.assertFalse("XML similar " + myDiff.toString(), myDiff.hasDifferences());
}
@Test
public void testDiff_withNormalizeAndIgnoreWhitespaces_shouldSucceed() {
// prepare testData
String controlXml = "<a><b>Test Value</b></a>";
String testXml = "<a>\n <b>\n Test\n Value\n </b>\n</a>";
// run test
Diff myDiff = DiffBuilder.compare(Input.fromString(controlXml).build())
.withTest(Input.fromString(testXml).build())
.normalizeWhitespace()
.ignoreWhitespace()
.build();
// validate result
Assert.assertFalse("XML similar " + myDiff.toString(), myDiff.hasDifferences());
}
@Test
public void testDiff_withCheckForIdentical_shouldFail() {
// prepare testData
String controlXml = "<a>Test Value</a>";
String testXml = "<a><![CDATA[Test Value]]></a>";
// run test
Diff myDiff = DiffBuilder.compare(Input.fromString(controlXml).build())
.withTest(Input.fromString(testXml).build())
.checkForIdentical()
.build();
// validate result
Assert.assertTrue(myDiff.toString(), myDiff.hasDifferences());
}
@Test
public void testDiff_withCheckForSimilar_shouldSucceed() {
// prepare testData
String controlXml = "<a>Test Value</a>";
String testXml = "<a><![CDATA[Test Value]]></a>";
// run test
Diff myDiff = DiffBuilder.compare(Input.fromString(controlXml).build())
.withTest(Input.fromString(testXml).build())
.checkForSimilar()
.build();
// validate result
Assert.assertFalse("XML similar " + myDiff.toString(), myDiff.hasDifferences());
}
@Test
public void testDiff_withoutIgnoreComments_shouldFail() {
// prepare testData
String controlXml = "<a><b><!-- A comment -->Test Value</b></a>";
String testXml = "<a><b><!-- An other comment -->Test Value</b></a>";
// run test
Diff myDiff = DiffBuilder.compare(Input.fromString(controlXml).build())
.withTest(Input.fromString(testXml).build())
.build();
// validate result
Assert.assertTrue(myDiff.toString(), myDiff.hasDifferences());
}
@Test
public void testDiff_withIgnoreComments_shouldSucceed() {
// prepare testData
String controlXml = "<a><b><!-- A comment -->Test Value</b></a>";
String testXml = "<a><b><!-- An other comment -->Test Value</b></a>";
// run test
Diff myDiff = DiffBuilder.compare(Input.fromString(controlXml).build())
.withTest(Input.fromString(testXml).build())
.ignoreComments()
.build();
// validate result
Assert.assertFalse("XML similar " + myDiff.toString(), myDiff.hasDifferences());
}
@Test
public void testDiff_withIgnoreComments2_0_shouldSucceed() {
// prepare testData
String controlXml = "<a><b><!-- A comment -->Test Value</b></a>";
String testXml = "<a><b><!-- An other comment -->Test Value</b></a>";
// run test
Diff myDiff = DiffBuilder.compare(Input.fromString(controlXml).build())
.withTest(Input.fromString(testXml).build())
.ignoreCommentsUsingXSLTVersion("2.0")
.build();
// validate result
Assert.assertFalse("XML similar " + myDiff.toString(), myDiff.hasDifferences());
}
@Test
public void testDiff_withIgnoreComments1_0_shouldSucceed() {
// prepare testData
String controlXml = "<a><b><!-- A comment -->Test Value</b></a>";
String testXml = "<a><b><!-- An other comment -->Test Value</b></a>";
// run test
Diff myDiff = DiffBuilder.compare(Input.fromString(controlXml).build())
.withTest(Input.fromString(testXml).build())
.ignoreCommentsUsingXSLTVersion("1.0")
.build();
// validate result
Assert.assertFalse("XML similar " + myDiff.toString(), myDiff.hasDifferences());
}
@Test
public void testDiff_fromCombinedSourceAndString_shouldSucceed() {
// prepare testData
String controlXml = "<a><b>Test Value</b></a>";
// run test
Diff myDiff = DiffBuilder.compare(Input.fromString(controlXml).build()).withTest(controlXml)
.build();
// validate result
Assert.assertFalse("XML similar " + myDiff.toString(), myDiff.hasDifferences());
}
@Test
public void testDiff_fromBuilder_shouldSucceed() {
// prepare testData
String controlXml = "<a><b>Test Value</b></a>";
// run test
Diff myDiff = DiffBuilder.compare(Input.fromString(controlXml))
.withTest(Input.fromString(controlXml))
.build();
// validate result
Assert.assertFalse("XML similar " + myDiff.toString(), myDiff.hasDifferences());
}
@Test
public void testDiff_fromByteArray_shouldSucceed() {
// prepare testData
byte[] controlXml = "<a><b>Test Value</b></a>".getBytes();
// run test
Diff myDiff = DiffBuilder.compare(controlXml).withTest(controlXml).build();
// validate result
Assert.assertFalse("XML similar " + myDiff.toString(), myDiff.hasDifferences());
}
@Test
public void testDiff_fromFile_shouldSucceed() {
// prepare testData
File controlXml = new File(TestResources.ANIMAL_FILE);
// run test
Diff myDiff = DiffBuilder.compare(controlXml).withTest(controlXml).build();
// validate result
Assert.assertFalse("XML similar " + myDiff.toString(), myDiff.hasDifferences());
}
@Test
public void testDiff_withComparisonListener_shouldCallListener() {
// prepare testData
final String control = "<a><b attr=\"abc\"></b></a>";
final String test = "<a><b attr=\"xyz\"></b></a>";
final List<Difference> diffs = new ArrayList<Difference>();
final ComparisonListener comparisonListener = new ComparisonListener() {
@Override
public void comparisonPerformed(Comparison comparison, ComparisonResult outcome) {
diffs.add(new Difference(comparison, outcome));
}
};
// run test
Diff myDiff = DiffBuilder.compare(control).withTest(test)
.withComparisonListeners(comparisonListener)
.build();
// validate result
Assert.assertTrue(myDiff.toString(), myDiff.hasDifferences());
assertThat(diffs.size(), greaterThan(1));
}
@Test
public void testDiff_withDifferenceListener_shouldCallListener() {
// prepare testData
final String control = "<a><b attr=\"abc\"></b></a>";
final String test = "<a><b attr=\"xyz\"></b></a>";
final List<Difference> diffs = new ArrayList<Difference>();
final ComparisonListener comparisonListener = new ComparisonListener() {
@Override
public void comparisonPerformed(Comparison comparison, ComparisonResult outcome) {
diffs.add(new Difference(comparison, outcome));
}
};
// run test
Diff myDiff = DiffBuilder.compare(control).withTest(test)
.withDifferenceListeners(comparisonListener)
.build();
// validate result
Assert.assertTrue(myDiff.toString(), myDiff.hasDifferences());
assertThat(diffs.size(), is(1));
assertThat(diffs.get(0).getResult(), is(ComparisonResult.DIFFERENT));
assertThat(diffs.get(0).getComparison().getType(), is(ComparisonType.ATTR_VALUE));
}
@Test
public void testDiff_withDifferenceEvaluator_shouldSucceed() {
// prepare testData
final String control = "<a><b attr=\"abc\"></b></a>";
final String test = "<a><b attr=\"xyz\"></b></a>";
// run test
Diff myDiff = DiffBuilder.compare(control).withTest(test)
.withDifferenceEvaluator(new IgnoreAttributeDifferenceEvaluator("attr"))
.build();
// validate result
Assert.assertFalse(myDiff.toString(), myDiff.hasDifferences());
}
@Test
public void testDiff_withDifferenceEvaluator_shouldNotInterfereWithSimilar() {
// prepare testData
final String control = "<a><b><![CDATA[abc]]></b></a>";
final String test = "<a><b>abc</b></a>";
// run test
Diff myDiff = DiffBuilder.compare(control).withTest(test)
.withDifferenceEvaluator(
DifferenceEvaluators.chain(DifferenceEvaluators.Default,
new IgnoreAttributeDifferenceEvaluator("attr")))
.checkForSimilar()
.build();
// validate result
Assert.assertFalse(myDiff.toString(), myDiff.hasDifferences());
}
@Test
public void testDiff_withCustomDifferenceEvaluator_shouldNotEvaluateSimilar() {
// prepare testData
final String control = "<a><b><![CDATA[abc]]></b></a>";
final String test = "<a><b>abc</b></a>";
// run test
Diff myDiff = DiffBuilder.compare(control).withTest(test)
.withDifferenceEvaluator(new IgnoreAttributeDifferenceEvaluator("attr"))
.checkForSimilar()
.build();
// validate result
Assert.assertTrue(myDiff.toString(), myDiff.hasDifferences());
Assert.assertThat(ComparisonResult.DIFFERENT, is(myDiff.getDifferences().iterator().next().getResult()));
}
@Test
public void testDiff_withDefaultComparisonController_shouldReturnAllDifferences() {
// prepare testData
final String control = "<a><b attr1=\"abc\" attr2=\"def\"></b></a>";
final String test = "<a><b attr1=\"uvw\" attr2=\"xyz\"></b></a>";
// run test
Diff myDiff = DiffBuilder.compare(control).withTest(test)
.build();
// validate result
Assert.assertTrue(myDiff.hasDifferences());
assertThat(count(myDiff.getDifferences()), is(2));
}
@Test
public void testDiff_withStopWhenDifferentComparisonController_shouldReturnOnlyFirstDifference() {
// prepare testData
final String control = "<a><b attr1=\"abc\" attr2=\"def\"></b></a>";
final String test = "<a><b attr1=\"uvw\" attr2=\"xyz\"></b></a>";
// run test
Diff myDiff = DiffBuilder.compare(control).withTest(test)
.withComparisonController(ComparisonControllers.StopWhenDifferent)
.build();
// validate result
Assert.assertTrue(myDiff.hasDifferences());
assertThat(count(myDiff.getDifferences()), is(1));
}
@Test
public void testDiff_withAttributeDifferences() {
// prepare testData
final String control = "<a><b attr1=\"abc\" attr2=\"def\"></b></a>";
final String test = "<a><b attr1=\"uvw\" attr2=\"def\"></b></a>";
// run test
Diff myDiff = DiffBuilder.compare(control).withTest(test)
.withComparisonController(ComparisonControllers.StopWhenDifferent)
.build();
// validate result
Assert.assertTrue(myDiff.hasDifferences());
assertThat(count(myDiff.getDifferences()), is(1));
// run test
Diff myDiffWithFilter = DiffBuilder.compare(control).withTest(test)
.withAttributeFilter(new Predicate<Attr>() {
@Override
public boolean test(Attr a) {
return !"attr1".equals(a.getName());
}
})
.withComparisonController(ComparisonControllers.StopWhenDifferent)
.build();
// validate result
Assert.assertFalse(myDiffWithFilter.hasDifferences());
}
@Test
public void testDiff_withExtraNodes() {
// prepare testData
String control = "<a><b></b><c/></a>";
String test = "<a><b></b><c/><d/></a>";
// run test
Diff myDiff = DiffBuilder.compare(control).withTest(test)
.withComparisonController(ComparisonControllers.StopWhenDifferent)
.build();
// validate result
Assert.assertTrue(myDiff.hasDifferences());
assertThat(count(myDiff.getDifferences()), is(1));
// run test
Diff myDiffWithFilter = DiffBuilder.compare(control).withTest(test)
.withNodeFilter(new Predicate<Node>() {
@Override
public boolean test(Node n) {
return !"d".equals(n.getNodeName());
}
})
.withComparisonController(ComparisonControllers.StopWhenDifferent)
.build();
// validate result
Assert.assertFalse(myDiffWithFilter.hasDifferences());
}
@Test
public void usesCustomComparisonFormatter() {
String control = "<a><b></b><c/></a>";
String test = "<a><b></b><c/><d/></a>";
Diff myDiff = DiffBuilder.compare(control).withTest(test)
.withComparisonController(ComparisonControllers.StopWhenDifferent)
.withComparisonFormatter(new DummyFormatter())
.build();
Assert.assertEquals("foo", myDiff.toString());
}
@Test
public void usesCustomComparisonFormatterForDifferences() {
String control = "<a><b></b><c/></a>";
String test = "<a><b></b><c/><d/></a>";
Diff myDiff = DiffBuilder.compare(control).withTest(test)
.withComparisonController(ComparisonControllers.StopWhenDifferent)
.withComparisonFormatter(new DummyFormatter())
.build();
Assert.assertEquals("foo (DIFFERENT)",
myDiff.getDifferences().iterator().next().toString());
}
@Test
public void usesDocumentBuilderFactory() throws Exception {
DocumentBuilderFactory dFac = Mockito.mock(DocumentBuilderFactory.class);
DocumentBuilder b = Mockito.mock(DocumentBuilder.class);
Mockito.when(dFac.newDocumentBuilder()).thenReturn(b);
Mockito.doThrow(new IOException())
.when(b).parse(Mockito.any(InputSource.class));
String control = "<a><b></b><c/></a>";
String test = "<a><b></b><c/><d/></a>";
try {
DiffBuilder.compare(control).withTest(test)
.withDocumentBuilderFactory(dFac).build();
Assert.fail("Expected exception");
} catch (XMLUnitException ex) {
Mockito.verify(b).parse(Mockito.any(InputSource.class));
}
}
/**
* Would cause an error because
* http://example.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
* doesn't exist if the DocumentBuilderFactory tried to resolve
* the DTD.
*
* @see "https://github.com/xmlunit/xmlunit/issues/86"
*/
@Test
public void usesDocumentBuilderFactoryWhenIgnoringWhitespace() throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setValidating(false);
dbf.setFeature("http://xml.org/sax/features/validation", false);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
Diff d = DiffBuilder.compare(Input.fromString(
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \n"
+ " \"http://example.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
+ " <head></head>\n"
+ " <body>some content 1</body>\n"
+ "</html>"))
.withTest(Input.fromString(
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \n"
+ " \"http://example.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
+ " <head></head>\n"
+ " <body>some content 2</body>\n"
+ "</html>"))
.withDocumentBuilderFactory(dbf)
.ignoreWhitespace()
.build();
Assert.assertTrue(d.hasDifferences());
}
@Test
public void usesDocumentBuilderFactoryWhenNormalizingWhitespace() throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setValidating(false);
dbf.setFeature("http://xml.org/sax/features/validation", false);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
Diff d = DiffBuilder.compare(Input.fromString(
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \n"
+ " \"http://example.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
+ " <head></head>\n"
+ " <body>some content 1</body>\n"
+ "</html>"))
.withTest(Input.fromString(
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \n"
+ " \"http://example.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
+ " <head></head>\n"
+ " <body>some content 2</body>\n"
+ "</html>"))
.withDocumentBuilderFactory(dbf)
.normalizeWhitespace()
.build();
Assert.assertTrue(d.hasDifferences());
}
@Test
public void testDiff_withoutIgnoreECW_shouldFail() {
// prepare testData
String controlXml = "<a><b>Test Value</b></a>";
String testXml = "<a>\n <b>Test Value</b>\n</a>";
// run test
Diff myDiff = DiffBuilder.compare(Input.fromString(controlXml).build())
.withTest(Input.fromString(testXml).build())
.build();
// validate result
Assert.assertTrue(myDiff.toString(), myDiff.hasDifferences());
}
@Test
public void testDiff_withIgnoreECW_shouldSucceed() {
// prepare testData
String controlXml = "<a><b>Test Value</b></a>";
String testXml = "<a>\n <b>Test Value</b>\n</a>";
// run test
Diff myDiff = DiffBuilder.compare(Input.fromString(controlXml).build())
.withTest(Input.fromString(testXml).build())
.ignoreElementContentWhitespace()
.build();
// validate result
Assert.assertFalse("XML similar " + myDiff.toString(), myDiff.hasDifferences());
}
/**
* Would cause an error because
* http://example.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
* doesn't exist if the DocumentBuilderFactory tried to resolve
* the DTD.
*/
@Test
public void usesDocumentBuilderFactoryWhenIgnoringElementContentWhitespace() throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setValidating(false);
dbf.setFeature("http://xml.org/sax/features/validation", false);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
Diff d = DiffBuilder.compare(Input.fromString(
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \n"
+ " \"http://example.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
+ " <head></head>\n"
+ " <body>some content 1</body>\n"
+ "</html>"))
.withTest(Input.fromString(
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \n"
+ " \"http://example.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
+ " <head></head>\n"
+ " <body>some content 2</body>\n"
+ "</html>"))
.withDocumentBuilderFactory(dbf)
.ignoreElementContentWhitespace()
.build();
Assert.assertTrue(d.hasDifferences());
}
private final class IgnoreAttributeDifferenceEvaluator implements DifferenceEvaluator {
private String attributeName;
public IgnoreAttributeDifferenceEvaluator(String attributeName) {
this.attributeName = attributeName;
}
@Override
public ComparisonResult evaluate(Comparison comparison, ComparisonResult outcome) {
final Node controlNode = comparison.getControlDetails().getTarget();
if (controlNode instanceof Attr) {
Attr attr = (Attr) controlNode;
if (attr.getName().equals(attributeName)) {
return ComparisonResult.EQUAL;
}
}
return outcome;
}
}
private static final class DummyFormatter implements ComparisonFormatter {
@Override
public String getDescription(Comparison difference) {
return "foo";
}
@Override
public String getDetails(Comparison.Detail details, ComparisonType type,
boolean formatXml) {
return "bar";
}
}
}