Third Generation Hamcrest matchers for better test cases

Hamcrest

What is Hamcrest?

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

HamcrestJUnit4
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 compileCompiles but fails
Hamvrest vs Junit 4 assertions

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.

TypeMatchers
Coreis – wrapper of equalTo
anything – matches always, we don’t care about the object
LogicalallOf if all matches = pass
anyOf
if any matches match = pass
not – if matcher does not match = pass
ObjectequalTo test equality
instanceOf
test type
notNullValue – test if value is not null
CollectionshasEntry test key, value pair
hasItem
test collection that contains elements
hasItemInArray – test element in an array
TextcontainsString – string matching
equalToIngoringCase – test string but ignoring case
NumbergreaterThan – test number ordering
Hamcrest Matchers

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


Vladimir Simonovski
Vladimir Simonovski

Software Automation Engineer for almost 5 years of experience. Involved in many QA activities for the insurance and banking platforms. Follow QAMIND on Twitter and LinkedIn to get fresh content around Software Testing.

Share this post

Leave a Reply

Your email address will not be published. Required fields are marked *

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