{{announcement.body}}
{{announcement.title}}

How to Make Your Own Hamcrest Matchers in Kotlin

DZone 's Guide to

How to Make Your Own Hamcrest Matchers in Kotlin

A Match made in Hamcrest-heaven!

· Java Zone ·
Free Resource

A Match made in Hamcrest-heaven!

Intro to Hamcrest Matchers

First things first, I should quickly explain what a Hamcrest Matcher is. When conducting unit tests, the built-in assertion types that come with the testing framework are generally pretty limited. They make it very easy for a person to end up with multiple asserts to essentially check one thing. Even if it doesn't contain multiple asserts, those asserts aren't the most fluent to read and don't tell exactly what you're checking.

You may also like: Hamcrest Containing Matchers

That's where Hamcrest Matchers come in (and other assertion libraries, but we're looking at Hamcrest right now). They allow you to define your own more robust and more fluent assertions, essentially. For example, if you were testing whether a method correctly returns an empty String, that test might look something like this:

Kotlin




x


1
@Test
2
fun testUsingMatcher() {
3
    val string = methodThatShouldReturnAnEmptyString();
4
    assertThat(string, isEmptyString());
5
}



Look at that assertion line. It almost reads like English once you ignore all the "punctuation". "Assert that string is an empty string." Some matchers put more effort into reading like English than others, but they all read well enough to make them easy to understand.

Anatomy of a Hamcrest Matcher

We'll go through the basics, recreating the IsEmptyString matcher along the way. First off, this matcher will extend from <code-BaseMatcher. With that, let's start building.

There are basically four parts to a Hamcrest Matcher: a static factory method, an assertion, a description of a passed assertion, and a description of a failed assertion. The last two are the biggest reason why I wrote this article. It took me a while with a fair bit of tinkering to figure out the "best practices" for that. But we'll start at the top.

In all honesty, the static factory method isn't an issue in Kotlin. the primary reason for it was to not need the new keyword, but Kotlin doesn't use it anyway. If you're planning on making it backward compatible for use in Java code, too, then I recommend still using it. If not, then the only thing you need to contend with is capitalization. Are you okay with a capitalized class name being used in the assertion? If so, then you've got it easy. If not, you have the option of either making the class name lower-cased (thus breaking convention) or adding a static factory method. There's also always the import ... as ... option that Kotlin so graciously provides.

If you're going to make the static factory, I actually recommend doing it as a top-level function, rather than as a companion object. This makes it automatically a static method for Java users, so they can do a nice static import, and it allows you to avoid the strange companion syntax (especially paired with @JvmStatic.

This code for the function is as simple as this:

Kotlin




xxxxxxxxxx
1


 
1
fun isEmptyString() = IsEmptyString()



Overall, the method is very bland and obvious. I have yet to run into an instance where it's not. It's also especially quick and easy with all the shortcuts Kotlin allows, such as skipping the return type and using the single expression form.

Next, we need the assertion, which is done with the matches() method. This is the real work of a matcher, running the actual check. Notice that it's not meant to throw the AssertionError; that's the job of the assertThat() method. This method simply returns a Boolean stating whether the input matches the idea the Matcher tests for.

The input into this method comes from the first argument in the assertThat() method. When assertThat() runs the provided matcher's matches() method, it passes that argument into it. The matches() method takes in an Object ( Any in Kotlin), not the type specified by the generics. This is because of some weird "feature" of Java generics that I haven't looked into, so I can't explain it. I just wanted you to know that you will generally have to do some sort of type checking when you just extend BaseMatcher.

Here's the implementation of our IsEmptyString's matches() method:

Kotlin




xxxxxxxxxx
1


 
1
override fun matches(actual: Any?): Boolean =
2
    if (actual is String)
3
        actual == ""
4
    else
5
        false



That's all there is to it in this case — it just checks whether it's an empty String.

Now, we have to do the two descriptions. We'll do these in tandem since they're similar. The two descriptions are describeTo() and describeMismatch(). The describeTo() method is used to describe what is expected by the matcher. In this case, that's an empty String. The  describeMismatch() method is for describing the actual result, usually just outputting the given object.

The two description methods only come into play if the match fails and the AssertionError is thrown. The assertThat() method then builds a Description for the failed assertion. It is formatted as follows:

Kotlin




xxxxxxxxxx
1


1
<user-provided reason>
2
Expected: <result of describeTo>
3
     but: <result of describeMismatch>



The user-provided reason can be defined by sending a String argument first into the assertThat() method.

As you can see, the formatting of the output is such that a person doesn't need to include any extraneous information in the descriptions, such as whether it is describing a match or mismatch.

What's weird about these two methods is that they don't use Strings, per se. They use what's called a Description object, which I'm pretty sure is what allows them to 1) avoid excessive String concatenation, since it seems to work a bit like a StringBuilder and 2) potentially use different kinds of Description (which is an interface) objects to display things a little differently in different tools.

So, here's our implementation of those "describe" methods:

Kotlin




xxxxxxxxxx
1


 
1
override fun describe(description: Description) {
2
    description.appendText("empty String")
3
}
4
 
          
5
override fun describeMismatch(item: Any?, description: Description) {
6
    description.appendValue(item)
7
}



You'll note that the Description object had the two methods appendText() and appendValue(). I've only ever used these two, even though it has a few more. Those are for a few rarer cases, and you should check them out if you're interested. If it wasn't clear, appendText will take a String and append it to the end of whatever's already in the description, just like a StringBuilder. And appendValue appears to run toString() on whatever you pass in and append that.

Until Next Time

That's it! You're done. You now have a working Hamcrest Matcher. For any more information, you should check out the official website. If you were to do your research, though, you'd discover that we didn't recreate the built-in IsEmptyString Matcher properly. There are a few techniques that are a little more advanced (and that would make this post get longer than I'm comfortable with). I will go over them in my next post.

Happy matching!

Further Reading

Hamcrest Containing Matchers

[DZone Refcard] Getting Started With Kotlin

Topics:
java ,kotlin ,hamcrest ,matching ,hamcrest matching ,tutorial ,string ,hamcrest matcher

Published at DZone with permission of Jake Zimmerman , DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}