What is Hamcrest?
Hamcrest is a framework that is used for writing matchers in the test declaratively. In other words, it allows you to set a custom assert implementations. These matches can be used in many testing frameworks.
First generation assertions
With the first-generation assert statement, we can check if a particular condition is satisfied. If it is not satisfied (false) then the test fails. One example is assert(statement).
Unfortunately, the error message wasn’t intuitive enough and that was the reason the second-generation statement was created.
Second generation assertions
Using assertEquals(statement)
was the second generation implementation of the unit test frameworks. But this would create many assert methods especially for large applications with many endpoints to test. The third-generation assertions were the saviors.
Third generation assertions
The third generation asserts uses assertThat(statement)
. It is more flexible and you will get more fluent error messages when the assertions fail. Also, you can define operations that can take a matcher as an argument and return them as a result of the assertion. We will go in-depth on this statement later.
Purpose of Hamcrest framework
The purpose of Hamcrest matchers is to make the tests as readable as possible. Let me give you an example. If we suppose to check that value should be 1 in a test we could write:
assertTrue(1, value);
or
assertThat(value, is(1));
In the first approach, we are testing that value must be 1 but it is very common to forget the order and type it backward. This could let to confusion in the error message and could cost you time just to detect where the issue is coming from.
With the second approach, it is impossible to make that mistake since you clearly see that the value must be 1. It’s all about the syntax that allows us to improve our assertions.
Also if the assertion fails, the first approach will only tell you that you got false but you expected true. The second approach will tell you what the assertion and what you got instead.
Hamcrest asserts vs JUnit 4 asserts
Hamcrest | JUnit4 |
assertThat(actual, is(equalTo(expected))) | assertEquals(expected, actual) |
java.lang.AssertionError: Expected: is “test” but: was 1 | java.lang.AssertionError: Expected :test Actual :1 |
Does not compile | Compiles but fails |
Hamcrest matchers
There are many matchers that we could go in detail but in this post, we will mention a couple of them just to get to know the concept behind the matchers.
Type | Matchers |
Core | is – wrapper of equalTo anything – matches always, we don’t care about the object |
Logical | allOf – if all matches = pass anyOf – if any matches match = pass not – if matcher does not match = pass |
Object | equalTo – test equality instanceOf – test type notNullValue – test if value is not null |
Collections | hasEntry – test key, value pair hasItem – test collection that contains elements hasItemInArray – test element in an array |
Text | containsString – string matching equalToIngoringCase – test string but ignoring case |
Number | greaterThan – test number ordering |
Using Hamcrest in a test
In order to use Hamcrest matchers in your tests, you need to add it in as a dependency in your repository. For example, for a Maven-based project, the following dependency needs to be added in the pom file.
<dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-library</artifactId> <version>2.2</version> <scope>test</scope> </dependency>
After that we need to import them using the wildcard import
import static org.hamcrest.CoreMatchers.*
or if you don’t prefer to use a wildcard import, then you can import them separately (better option).
import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat;
Then we can start writing our simple test.
@Test public void simpleHamcrestAssertsTest() { JSONObject json = new JSONObject(); json.put("name", "QAMIND"); json.put("year", 2020); json.put("content", Arrays.asList("Tutorials","Reviews","How To's")); assertThat(json, notNullValue()); assertThat(json.size(), equalTo(3)); assertThat(json, hasEntry("name","QAMIND")); assertThat(json, hasEntry("year",2020)); assertThat(json, hasEntry("content", Arrays.asList("Tutorials","Reviews","How To's"))); }
We create a JSON object with some key, value pairs and we assert them by using the hasEntry matcher which checks the key and its value. We’re asserting a string and integer and a list of strings. Also, we are checking that the JSON object has size = 3 which are the actual properties included.
If we intentionally try to make the test fail for example lets change the expected “QAMIND” value to “QAMRING”. The error response we get is:
java.lang.AssertionError: Expected: map containing [“name”->”QAMRIND”] but: map was [, , ]
Of course, we can customize the response to be more readable, but for the purpose of getting to know the basic Hamcrest concepts, we’re going to stick with this basic error message.
Custom Matchers
Writing a custom matcher could be very helpful especially if you need to check the same thing but for a different test case or if you need to customize it to the level you want to check for a specific scenario.
Matcher Skeleton
We’re going to go with an example of an existing built-in abstract class in Hamcrest called FeatureMatcher. If you open this class you will notice that it has featureValueOf() abstract method which returns a value which is passed in the matchesSafely() method.
@Test public void customHamcrestMatcherTest() { JSONObject json = new JSONObject(); json.put("name", "QAMIND"); json.put("year", 2020); assertThat(json, size(is(2))); } private static Matcher<JSONObject> size(Matcher<? super Integer> matcher) { return new FeatureMatcher<JSONObject, Integer>(matcher,"Body has size", "size") { @Override protected Integer featureValueOf(JSONObject actual) { return actual.size(); } }; }
We provide a custom matcher to check the size of the JSON body. In the static method, we return a new FeatureMatcher that accepts the matcher we want to use, a description of the scenario we want to assert, and a description for the possible mismatch. We overwrite the featureValueOf() method which accepts JSONObject and returns its size. After that, we can use this matcher to check the size of our body with:
assertThat(json, size(is(2)));
That’s it, our custom matcher is finished and used in a test. This can be reused in many scenarios where we need to check the size of the object.
The Bottom Line
You have now seen what it’s like to use Hamcrest for assertions in your tests. It has advantages over the older generations like better readability of the assertions and the error messages, it offers a great level of customization and with its custom matchers we can have all of its benefits wrapped in a nice block of code that can be reusable for different scenarios.
Latest Posts
- Test Analyst’s Responsibilities in Risk-Based Testing
- Crucial Aspects Of Superior Test Code Quality
- Streamline Your Cucumber BDD Tests with Gherkin Lint: A Comprehensive Guide
- How to Test Authentication and Authorization with Confidence + Best Tips
- Exploring the Three Scrum Artifacts: Grasping Their Essence and Effective Utilization
Share this post