Advanced Creation of Hamcrest Matchers
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 comment that I had used the “yoda condition” to avoid a null check as well as a type check. First off, it wouldn’t hurt to do a little bit of research on yoda conditions yourself (I may put out an article about it someday, but no guarantees), but the biggest thing to note here is that some sort of type check and null check is needed. This is because the matches() method takes in an object, not the type specified in the generics argument.
As is described in Hamcrest’s documentation:
This method matches against Object, 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 Object; 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 week, 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 do 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.
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 Static Imports
After writing a fair few tests with Hamcrest Matchers, you’ll probably notice that you have quite a few static 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 static imports by creating a new class that essentially does it for you. This new class has those annoying static imports, but then it defines its own static factory methods that delegate to the originals. Here’s an example of combining some core Matchers into one place:
import org.hamcrest.core.IsEqual; import org.hamcrest.core.IsNull; import org.hamcrest.core.IsSame; import org.hamcrest.Matcher; public class CoreMatchers { public static Matcher equalTo(T object) { return IsEqual.equalTo(object); } public static Matcher notNullValue() { return IsNull.notNullValue(); } public static Matcher notNullValue(Class type) { return IsNull.notNullValue(type); } public static Matcher nullValue() { return IsNull.nullValue(); } public static Matcher nullValue(Class type) { return IsNull.nullValue(type); } public static Matcher sameInstance(T target) { return IsSame.sameInstance(target); } public static Matcher theInstance(T target) { return IsSame.theInstance(target); } }
Then, to use any or all of those Matchers, you only need to do a static import of CoreMatchers.*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.
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. 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. 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“.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.
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 week, 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 their assertions.
Reference: | Advanced Creation of Hamcrest Matchers from our JCG partner Jacob Zimmerman at the Programming Ideas With Jake blog. |
Your CoreMatchers already exists. Its called:
org.hamcrest.Matchers