QR code

XML/XPath Matchers for Hamcrest

xml hamcrest xpath testing


Hamcrest is my favorite instrument in unit testing. It replaces the JUnit procedural assertions of org.junit.Assert with an object-oriented mechanism. However, I will discuss that subject in more detail sometime later.

Now, though, I want to demonstrate a new library published today on GitHub and Maven Central: jcabi-matchers. jcabi-matchers is a collection of Hamcrest matchers to make XPath assertions in XML and XHTML documents.

Let's say, for instance, a class that is undergoing testing produces an XML that needs to contain a single <message> element with the content "hello, world!"

This is how that code would look in a unit test:

import com.jcabi.matchers.XhtmlMatchers;
import org.hamcrest.MatcherAssert;
import org.junit.Test;
public class FooTest {
  public void hasWelcomeMessage() {
      new Foo().createXml(),
        "/document/message[.='hello, world!']"

There are two alternatives to the above that I'm aware of, which are do almost the same thing: xml-matchers by David Ehringer and hasXPath() method in Hamcrest itself.

I have tried them both, but faced a number of problems.

First, Hamcrest hasXPath() works only with an instance of Node. With this method, converting a String into Node becomes a repetitive and routine task in every unit test.

The above is a very strange limitation of Hamcrest in contrast to jcabi-matchers, which works with almost anything, from a String to a Reader and even an InputStream.

Second, XmlMatchers from xml-matchers provides a very inconvenient way for working with namespaces. Before you can use an XPath query with a non-default namespace, you should create an instance of NamespaceContext.

The library provides a simple implementation of this interface, but, still, it is requires extra code in every unit test.

jcabi-matchers simplifies namespace handling problems even further, as it pre-defines most popular namespaces, including xtml, xs, xsl, etc.

The following example works right out-of-the-box—without any extra configuration:

  new URL("http://www.google.com").getContent(),

To summarize, my primary objective with the library was its simplicity of usage.