Thursday, December 3, 2009

Simple XML Testing from Java

2009 is coming to a close and I'm writing a blog post about Java and XML. The blog's called "behind the times" for a reason: no fancy dynamic languages, JSON, or REST. Straight up XML on Java action.

One of my favorite editions to our enterprise toolset this year was XMLUnit. We'd been using Groovy in testing, and multi-line strings led to a ton of expressiveness in test cases... but then we stopped using Groovy. I switched back to Java testing and was left with making String based assertions and doing String#contains() calls. Not ideal; enter XMLUnit.

XMLUnit supports XML based equality comparisons so that things like these two snippets are equal:

<root/>

<root></root>

As well as these:
<root>data</root>

<root>
data
</root>

In fact, there's a whole gaggle of options around what is a meaningful versus a meaningless difference. For instance, I often consider two dates equal as long as they are formatted correctly (ignoring those pesky timestamp issue is nice).

The problem with XMLUnit is that the API is not as nice for the simple case as it could be. What I want is a simple custom assertion that asserts two XML snippets as similar. Something like this:
XmlAssertions.assertXmlSimilar("<root/>", "<root></root>"); 
XmlAssertions.assertXmlSimilar("<root>data</root>", "<root>\ndata\n</root>\n");

This was as simple as creating a small utility class and hiding the XMLUnit API behind a nicer custom assertion:
import java.util.List;
import junit.framework.*;
import org.custommonkey.xmlunit.*;

public class XmlAssertions {

private static final String ERROR_MSG = "XML comparison failure. \nExpected: %s\nReceived: %s\n%s";

static {
XMLUnit.setIgnoreWhitespace(true);
}

public static void assertXmlSimilar(String expected, String actual) {
try {
Diff diff = new Diff(expected, actual);
List differences = new DetailedDiff(diff).getAllDifferences();
Assert.assertTrue(
String.format(ERROR_MSG, expected, actual, differences),
diff.similar());
} catch (Exception ex) {
Assert.fail(String.format(ERROR_MSG, expected, actual, ex.getMessage()));
}
}
}

The raw XMLUnit code is clearly not something you want to see within the body of a test method. The custom assertion seems simple, but it's made a real pleasure of writing XML based functional tests around all our web services. Feel free to steal my code here: http://svn.assembla.com/svn/SampleCode/xmlunit/XmlAssertions.java

6 comments:

R.J. Salicco said...

Great post. In the past, I have tested Web services from a client perspective; if the client works, then everything is good. I like the idea of creating tests for XML results along side the Web service's unit tests.

Hamlet D'Arcy said...

SoapUI works really well too but it takes you out of the IDE and programming mode. We actually shared SoapUI with out QA team with pretty good success.

Rorick said...

Hello. I'm your reader for some time and I really like your blog. Usually I reluctant to comment but this post left me in some confusion. I've used XmlUnit recently and needed exactly one method of it: XMLAssert.assertXMLEqual(String, String).
Isn't it your assertXmlSimilar in fact?

Hamlet D'Arcy said...

assertXMLEquals is great until your test fails. But if the input is malformed then a plain old SAXException is thrown with a cryptic error message.

The helper method is really about giving XMLUnit better error messages on failures.

Lowell Kirsh said...

You have a bug in the code linked to at http://svn.assembla.com/svn/SampleCode/xmlunit/XmlAssertions.java

You need to have a call to Assert.fail() right before the last catch block.

I make that same mistake way too often...

Hamlet D'Arcy said...

@lowellk
I'm pretty sure I don't want another failure placed right before the catch block. That would cause the test to fail every time.