Wednesday, July 30, 2008

XML Unit Testing with Groovy and XMLUnit

A while back I needed to compare XML documents during my unit testing. The documents, or snippets actually, were small enough and I had enough control over how they were created, that simple comparisons of the expected and actual results were enough. After a while as the snippets grew larger I ran into a few problems. For one, if I didn't use StreamingMarkupBuilder to create the XML, whitespace became an issue. And more importantly, "equivalent" XML would compare differently if the order of the elements of a node were not in the same order.

Eventually, I found a few lines of code in the Groovy user guide that turned me onto XMLUnit, at the bottom of the first example:
Groovy Example

The following line quickly took care of the whitespace issue.
import org.custommonkey.xmlunit.*
XMLUnit.setIgnoreWhitespace(true)
For testing equivalent XML, XMLUnit allows to expect child elements in a exact order (identical) or not (similar):
def photoguy = new StreamingMarkupBuilder().bind {
person {
firstname('Larry')
lastname('Appleton')
}
}

// equivalent, but different ordered child elements
def balkisCousin = new StreamingMarkupBuilder().bind {
person {
lastname('Appleton')
firstname('Larry')
}
}

def xmlDiff = new Diff(photographer, balkisCousin)
assert ! XmlDiff.identical()
assert xmlDiff.similar()
Down the road, I ran into the situation where I wanted to allow a difference in a particular element. The element contents were quite large and subject to change and I didn't really need to cover the results in this part of my project. Furthermore, I didn't want to store the actual result as it was that particular day in my test resources I used for the expected results. Naively, I could make an assertion about every other element in the snippet I cared about and ignore the one that might produce a difference, but that wouldn't be good for my mental health.

Luckily, XMLUnit gave me what I needed, in this case I'm allowing one difference:
String expectedXml = getClass().getClassLoader().getResourceAsStream(testFile).getText()
def xmlDiff = new Diff(personAsXml, expectedXml)
assert !xmlDiff.similar()
assert !xmlDiff.identical()

def detailedDiff = new DetailedDiff(xmlDiff)
def differences = detailedDiff.getAllDifferences()
assertEquals detailedDiff.toString(), 1, differences.size()

def location = differences.pop().getTestNodeDetail().getXpathLocation()
// a regex may be better for this?
def expectedLocation = '/person[1]/employment[1]/company[1]/text()[1]'
assertEquals expectedLocation, location
XMLUnit has a whole bunch of other features that I have not gone back to check out yet. Maybe somebody else has and would be kind enough to drop a comment. A new version, 1.2, came out in June.

1 comment: