Last active
August 6, 2021 21:19
-
-
Save sleberknight/b112926d51da98617161ade108fb4450 to your computer and use it in GitHub Desktop.
Playing around with XmlUnit and XmlAssert to log all differences
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.kiwiproject.test.playground; | |
import static java.util.Objects.nonNull; | |
import lombok.extern.slf4j.Slf4j; | |
import org.junit.jupiter.api.DisplayName; | |
import org.junit.jupiter.api.Test; | |
import org.junit.jupiter.api.TestInfo; | |
import org.kiwiproject.test.util.Fixtures; | |
import org.xmlunit.assertj3.CompareAssert; | |
import org.xmlunit.assertj3.XmlAssert; | |
import org.xmlunit.diff.Comparison; | |
import org.xmlunit.diff.ComparisonControllers; | |
import org.xmlunit.diff.ComparisonListener; | |
import org.xmlunit.diff.ComparisonResult; | |
import java.util.concurrent.atomic.AtomicBoolean; | |
@DisplayName("Using XmlUnit to log all the differences") | |
class XmlUnitLogAllDifferencesTest { | |
// This passes | |
@Test | |
void shouldCompareIdentical() { | |
var alice1 = Fixtures.fixture("alice-smith-1.xml"); | |
var alice2 = Fixtures.fixture("alice-smith-2.xml"); | |
XmlAssert.assertThat(alice1).and(alice2).areIdentical(); | |
} | |
// This passes | |
@Test | |
void shouldCompareCaseInsensitive() { | |
var alice1 = Fixtures.fixture("alice-smith-1.xml"); | |
var alice2 = Fixtures.fixture("alice-smith-3.xml"); | |
XmlAssert.assertThat(alice1).and(alice2).ignoreWhitespace().areIdentical(); | |
} | |
// This FAILS and logs ALL the differences (does not include test name in log output) | |
@Test | |
void shouldLogAllDifferencesUsingLoggingComparisonListener() { | |
var aliceSmith = Fixtures.fixture("alice-smith-1.xml"); | |
var aliceJones = Fixtures.fixture("alice-jones.xml"); | |
XmlAssert.assertThat(aliceSmith) | |
.and(aliceJones) | |
.withComparisonController(ComparisonControllers.Default) | |
.withComparisonListeners(new LoggingComparisonListener()) | |
.areIdentical(); | |
} | |
// This FAILS and logs ALL the differences (includes test name in log output) | |
@Test | |
void shouldLogAllDifferencesAndTestNameUsingLoggingComparisonListener(TestInfo testInfo) { | |
var aliceSmith = Fixtures.fixture("alice-smith-1.xml"); | |
var aliceJones = Fixtures.fixture("alice-jones.xml"); | |
XmlAssert.assertThat(aliceSmith) | |
.and(aliceJones) | |
.withComparisonController(ComparisonControllers.Default) | |
.withComparisonListeners(new LoggingComparisonListener(testInfo)) | |
.areIdentical(); | |
} | |
// This FAILS and logs ALL the differences using the simple OpinionatedXmlAssert (includes test name in log output) | |
@Test | |
void shouldUseOpinionatedXmlAssertToHideTheDetails(TestInfo testInfo) { | |
var aliceSmith = Fixtures.fixture("alice-smith-1.xml"); | |
var aliceJones = Fixtures.fixture("alice-jones.xml"); | |
OpinionatedXmlAssert.assertThat(aliceSmith).withTestNameFrom(testInfo).and(aliceJones).areIdentical(); | |
} | |
/** | |
* Sits on top of {@link XmlAssert} to provide a simple way to compare all differences and log them as they occur. | |
* <p> | |
* This matters only because by default {@link XmlAssert} stops comparing when it finds the first difference, and | |
* only reports that difference. So if an XML comparison has multiple differences, you will only see the first | |
* one and won't know about any others until you've fixed the first one, then re-run the test, and then continue | |
* until all other comparison failures are fixed. | |
*/ | |
static class OpinionatedXmlAssert { | |
private final Object o; | |
private String testName; | |
private OpinionatedXmlAssert(Object o) { | |
this.o = o; | |
} | |
// Entry point | |
static OpinionatedXmlAssert assertThat(Object o) { | |
return new OpinionatedXmlAssert(o); | |
} | |
// Optionally include the test name in the logging output when there are differences | |
OpinionatedXmlAssert withTestNameFrom(TestInfo testInfo) { | |
return withTestName(testInfo.getDisplayName()); | |
} | |
// Optionally include the test name in the logging output when there are differences | |
OpinionatedXmlAssert withTestName(String testName) { | |
this.testName = testName; | |
return this; | |
} | |
// Terminal operation in OpinionatedXmlAssert; you get a CompareAssert that has been configured with the | |
// "opinionated" settings of using the default ComparisonController and the LoggingComparisonListener | |
CompareAssert and(Object control) { | |
return XmlAssert.assertThat(o) | |
.and(control) | |
.withComparisonController(ComparisonControllers.Default) | |
.withComparisonListeners(new LoggingComparisonListener(testName)); | |
} | |
} | |
/** | |
* Logs differences in XML comparison results. | |
*/ | |
@Slf4j | |
static class LoggingComparisonListener implements ComparisonListener { | |
private final String testName; | |
private final AtomicBoolean haveSeenDifferenceAlready; | |
LoggingComparisonListener() { | |
this((String) null); | |
} | |
LoggingComparisonListener(TestInfo testInfo) { | |
this(testInfo.getDisplayName()); | |
} | |
LoggingComparisonListener(String testName) { | |
this.testName = testName; | |
this.haveSeenDifferenceAlready = new AtomicBoolean(); | |
} | |
@Override | |
public void comparisonPerformed(Comparison comparison, ComparisonResult outcome) { | |
if (outcome == ComparisonResult.DIFFERENT) { | |
logDifference(comparison); | |
} | |
} | |
private void logDifference(Comparison comparison) { | |
if (haveSeenDifferenceAlready.compareAndSet(false, true)) { | |
logPreamble(); | |
} | |
LOG.warn(comparison.toString()); | |
} | |
private void logPreamble() { | |
var testNameOrEmpty = nonNull(testName) ? (testName + ": ") : ""; | |
LOG.warn("{}XML differences found:", testNameOrEmpty); | |
} | |
} | |
} | |
/* | |
The test fixtures used above in the tests are shown below. | |
They are directly under src/test/resources. | |
Also note we use the Fixtures class from kiwi-test. | |
See here: https://github.com/kiwiproject/kiwi-test | |
alice-smith-1.xml: | |
<person> | |
<first_name>Alice</first_name> | |
<last_name>Smith</last_name> | |
<age>42</age> | |
<country>USA</country> | |
</person> | |
alice-smith-2.xml is a copy of alice-smith-1.xml | |
alice-smith-3.xml: | |
<person><first_name>Alice</first_name><last_name>Smith</last_name><age>42</age><country>USA</country></person> | |
alice-jones.xml: | |
<person> | |
<first_name>Alice</first_name> | |
<last_name>Jones</last_name> | |
<age>29</age> | |
<country>USA</country> | |
</person> | |
Sample output from the shouldUseOpinionatedXmlAssertToHideTheDetails test: | |
17:14:17.875 [main] WARN org.kiwiproject.test.playground.XmlUnitLogAllDifferencesTest$LoggingComparisonListener - shouldUseOpinionatedXmlAssertToHideTheDetails(TestInfo): XML differences found: | |
17:14:17.878 [main] WARN org.kiwiproject.test.playground.XmlUnitLogAllDifferencesTest$LoggingComparisonListener - Expected text value 'Jones' but was 'Smith' - comparing <last_name ...>Jones</last_name> at /person[1]/last_name[1]/text()[1] to <last_name ...>Smith</last_name> at /person[1]/last_name[1]/text()[1] | |
17:14:17.879 [main] WARN org.kiwiproject.test.playground.XmlUnitLogAllDifferencesTest$LoggingComparisonListener - Expected text value '29' but was '42' - comparing <age ...>29</age> at /person[1]/age[1]/text()[1] to <age ...>42</age> at /person[1]/age[1]/text()[1] | |
org.opentest4j.AssertionFailedError: | |
Expecting: | |
<control instance> and <test instance> to be identical | |
Expected text value 'Jones' but was 'Smith' - comparing <last_name ...>Jones</last_name> at /person[1]/last_name[1]/text()[1] to <last_name ...>Smith</last_name> at /person[1]/last_name[1]/text()[1] | |
expected:<<last_name>Jones</last_name>> but was:<<last_name>Smith</last_name>>> | |
Expected :<last_name>Jones</last_name> | |
Actual :<last_name>Smith</last_name> | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment