This article is a rewrite of an older one done in Java. This one is done in Kotlin instead.
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.
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
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
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.
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
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
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.
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:
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”.
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.