Kotlin

Advanced Creation of Hamcrest Matchers in Kotlin

This article is a rewrite of an older one done in Java. This one is done in Kotlin instead.

Intro

Last time, I went over what a Hamcrest Matcher was, how it’s used, and how to make one. In this article, I will explain more advanced steps in the creation of Hamcrest Matchers. First, I’ll share how to make your matchers more easily type-safe, then some techniques for stateless Matchers, then finally how to cut down on so many static imports on your test classes. I’ll also give some quick tips on naming your static factory methods.

Typesafe Matchers

You may have noticed in the matches() method that we developed last time, I put in a type check. Potentially, you’ll need null checks too, because the method accepts an Any?, which allows nulls. The type check should seem strange, since we inherited from a class that has a generic type that we specified to be a String.

As is described in Hamcrest’s documentation, though:
This method matches against [Any?], instead of the generic type T. This is because the caller of the Matcher does not know at runtime what the type is (because of type erasure with Java generics).

Because of this, we need to make sure of the type of the object being passed in. Also, we should make sure there are no nulls being passed in (unless our specific Matcher is okay with that, but that’s super rare), or at least make certain that a null being passed in won’t cause a NullPointerException.

But there’s an easier way: the TypeSafeMatcher. If you extend this class instead of the BaseMatcher class, it’ll do the type checking and null checking for you, then pass the object to a matching method that only takes the generics-specified type.

Defining a TypeSafeMatcher is very similar to defining a Matcher the way we did last time, with a few differences: instead of overriding matches(), you override matchesSafely() which takes in the generic type instead of Any?; and instead of overriding describeMismatch(), you override describeMismatchSafely(). It may be a surprise that there isn’t a new describeTo(), but seeing as that doesn’t take in anything other than the Description, there’s no need for a type safe version.

Otherwise, creating the TypeSafeMatcher is very much the same.

I have to mention something that I forgot last time, though. Someone who is defining their own Matchers doesn’t need to override the describeMismatch() or describeMismatchSafely() methods. BaseMatcher and TypeSafeMatcher both have default implementations of those methods that simply output “was item.toString()” ( or “was a itemClassName(item.toString())” if the TypeSafeMatcher gets an item of an incorrect type).

These default implementations are generally good enough, but if a type being worked with doesn’t have a useful implementation of toString(), it’s obviously more useful to use your own mismatch message that describes what is wrong with the item. I always do, even if the class has a decent toString() implementation, since it can direct a little more quickly to the problem.

A Note About Other Extendable Matcher Classes

There are several other Matcher classes in the Hamcrest core library that are meant for users to extend from. These come in a few flavors.

First off, there’s CustomMatcher and CustomTypeSafeMatcher. These are designed for making one-off Matchers via anonymous classes. They can be useful, but I’d prefer to always make a proper implementation in case I ever need it again.

Next, there’s the DiagnosingMatcher and the TypeSafeDiagnosingMatcher, which have you create the mismatch description within the matches() method. This would seem like a nice way to kill two birds with one stone, but I have several beefs with it: 1) it violates the SRP 2) if there’s a mismatch, it makes a second call to the matches() method just to fill in the mismatch description. So the first call ignores getting the description, and the second ignores the matching.

The last special Matcher that you can extend is the FeatureMatcher. This can be fairly useful, but it’s complicated to understand (I’m not sure if I understand it correctly – not until I try making one of my own or reading up on how to do one). If I figure it out and gain a good understanding, I’ll write another post here for you.
Now let’s look at some more advanced things you can try with your Matchers.

Stateless Matchers

Any Matcher that doesn’t require anything passed into its constructor (and therefore, it’s static factory method) is a stateless Matcher. They have a nice little advantage over other Matchers in that you only need a single instance of it to exist at any point, which can be reused any time you need to use that Matcher.

This is a really simple addition. All you need to do is create a static instance of the class and have your static factories return that instance instead of calling the constructor. The IsEmptyString Matcher that actually comes with library does this (our example last time didn’t, but that was for simplicity’s sake).

Reducing the Number of Imports

After writing a fair few tests with Hamcrest Matchers, you’ll probably notice that you have quite a few imports at the top of your file. This can become a big fat nuisance after a while, so let’s look at something to reduce this problem.

This is actually almost as simple of a solution as the last one. You can reduce the imports by creating a new file that essentially does it for you. This new class has those annoying imports, but then it defines its own static factory methods that delegate to the originals. This is especially good for combining a bunch of Matchers for one type into one import. Here’s an example of combining some core Matchers into one place:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
import org.hamcrest.core.IsEqual;
import org.hamcrest.core.IsNull;
import org.hamcrest.core.IsSame;
import org.hamcrest.Matcher;
 
fun isEqualTo(T object) = IsEqual.equalTo(object)
 
fun isNotANullValue() = IsNull.notNullValue()
 
fun isNotANullValue(Class type) = IsNull.notNullValue(type)
 
fun isANullValue() = IsNull.nullValue()
 
fun isANullValue(Class type) = IsNull.nullValue(type)
 
fun isTheSameInstanceAs(T target) = IsSame.sameInstance(target)
 
fun isTheInstance(T target) = IsSame.theInstance(target)

With this file defined, to use any or all of those Matchers, you only need to do a import of module name.* There is also a way generate these combined Matcher classes, shown on the official Hamcrest tutorials. I won’t go over it, since it’s outside the scope of this article, and I’m not a fan of it.

If you don’t like how this leads to extra stack layers and such, you could always make the functions inlined. Seems to me like a great place for it.

Closing Tips: Naming

If you go through the official Hamcrest tutorial and/or look over built-in Matchers, you may notice a trend for the naming of the static factory methods. The general grammar matches “assert that testObject is factoryMethod“. The grammar of the method name is generally designed to be a present tense action that can be preceded with “is”. When naming your own static factory methods, you should usually follow this convention, but I actually suggest putting “is” into the name already, as you may have noticed with the code above. That way, users of your Matcher don’t need to nest your method inside the is() method. If you do this, though, you will need to create the inverse function too. If IsNull is good enough to get the treatment, there’s no reason yours shouldn’t. The reason to allow the is() method to wrap your Matcher is so you can also wrap it in the not() method to test the inverse of what you’re already testing. This leads to a sentence like “assert that testObject is not factoryMethod“. But if you define the two functions named that way, you don’t have to worry about all of those pointless parenthesis while writing your tests.

If you feel that following the convention is too restrictive for your specific Matcher, just make sure you’re using a present tense action test. For example, I made a matcher that checks for an exception being thrown whose static factory method is throwsA(). I just didn’t like naming it throwingA() in order to work with “is”. But, again, if you break the convention, you have to be certain to create an inverse static factory method; doesntThrowA(), for example. If you’re implementing your own inverse factories, the simplest way to do so is usually to wrap your positive factory with not(). So, my doesntThrowA() method would return not(throwsA()). Be careful, though: simply reversing true and false sometimes doesn’t actually give the proper inverse you’re going for. For example, the inverse of “check that all items in a collection meet a criteria” is not “none of the items meet the criteria”.

Outro

Well, that’s all I have for you. If there’s anything else about Hamcrest Matchers you’d like me to go over, let me know in the comments. Otherwise, you can do your own research on Hamcrest Matchers on its github page. Next time, I’m going to go over how you can get your Hamcrest Matchers to check multiple things in a similar fluent way that AssertJ does with their assertions.

Published on Java Code Geeks with permission by Jacob Zimmerman, partner at our JCG program. See the original article here: Advanced Creation of Hamcrest Matchers in Kotlin

Opinions expressed by Java Code Geeks contributors are their own.

Jacob Zimmerman

Jacob is a certified Java programmer (level 1) and Python enthusiast. He loves to solve large problems with programming and considers himself pretty good at design.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Waheed Akhtar
3 years ago

Kotlin beginner but loving it. Your website is my source of power. Each and every tutorial is very well explained. Keep teaching.

Back to top button