Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

Hamcrest provides static matchers to help make unit test assertions simpler and more legible. You can get started exploring some of the available matchers here.

In this article, we’ll dive deeper into the number-related matchers.

2. Setup

To get Hamcrest, we just need to add the following Maven dependency to our pom.xml:

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>java-hamcrest</artifactId>
    <version>2.0.0.0</version>
</dependency>

The latest Hamcrest version can be found on Maven Central.

3. Proximity Matchers

The first set of matchers that we’re going to take a look at are the ones that check if some element is close to a value +/- an error.

More formally:

value - error <= element <= value + error

If the comparison above is true, the assertion will pass.

Let’s see it in action!

3.1. isClose With Double Values

Let’s say that we have a number stored in a double variable called actual. And, we want to test if actual is close to 1 +/- 0.5.

That is:

1 - 0.5 <= actual <= 1 + 0.5
    0.5 <= actual <= 1.5

Now let’s create a unit test using isClose matcher:

@Test
public void givenADouble_whenCloseTo_thenCorrect() {
    double actual = 1.3;
    double operand = 1;
    double error = 0.5;
 
    assertThat(actual, closeTo(operand, error));
}

As 1.3 is between 0.5 and 1.5, the test will pass. Same way, we can test the negative scenario:

@Test
public void givenADouble_whenNotCloseTo_thenCorrect() {
    double actual = 1.6;
    double operand = 1;
    double error = 0.5;
 
    assertThat(actual, not(closeTo(operand, error)));
}

Now, let’s take a look at a similar situation with a different type of variables.

3.2. isClose With BigDecimal Values

isClose is overloaded and can be used same as with double values, but with BigDecimal objects:

@Test
public void givenABigDecimal_whenCloseTo_thenCorrect() {
    BigDecimal actual = new BigDecimal("1.0003");
    BigDecimal operand = new BigDecimal("1");
    BigDecimal error = new BigDecimal("0.0005");
    
    assertThat(actual, is(closeTo(operand, error)));
}

@Test
public void givenABigDecimal_whenNotCloseTo_thenCorrect() {
    BigDecimal actual = new BigDecimal("1.0006");
    BigDecimal operand = new BigDecimal("1");
    BigDecimal error = new BigDecimal("0.0005");
    
    assertThat(actual, is(not(closeTo(operand, error))));
}

Please note that the is matcher only decorates other matchers without adding extra logic. It just makes the whole assertion more readable.

That’s about it for proximity matchers. Next, we’ll take a look at order matchers.

4. Order Matchers

As their name says, these matchers help make assertions regarding the order.

There are five of them:

  • comparesEqualTo
  • greaterThan
  • greaterThanOrEqualTo
  • lessThan
  • lessThanOrEqualTo

They’re pretty much self-explanatory, but let’s see some examples.

4.1. Order Matchers With Integer Values

The most common scenario would be using these matchers with numbers.

So, let’s go ahead and create some tests:

@Test
public void given5_whenComparesEqualTo5_thenCorrect() {
    Integer five = 5;
    
    assertThat(five, comparesEqualTo(five));
}

@Test
public void given5_whenNotComparesEqualTo7_thenCorrect() {
    Integer seven = 7;
    Integer five = 5;

    assertThat(five, not(comparesEqualTo(seven)));
}

@Test
public void given7_whenGreaterThan5_thenCorrect() {
    Integer seven = 7;
    Integer five = 5;
 
    assertThat(seven, is(greaterThan(five)));
}

@Test
public void given7_whenGreaterThanOrEqualTo5_thenCorrect() {
    Integer seven = 7;
    Integer five = 5;
 
    assertThat(seven, is(greaterThanOrEqualTo(five)));
}

@Test
public void given5_whenGreaterThanOrEqualTo5_thenCorrect() {
    Integer five = 5;
 
    assertThat(five, is(greaterThanOrEqualTo(five)));
}

@Test
public void given3_whenLessThan5_thenCorrect() {
   Integer three = 3;
   Integer five = 5;
 
   assertThat(three, is(lessThan(five)));
}

@Test
public void given3_whenLessThanOrEqualTo5_thenCorrect() {
   Integer three = 3;
   Integer five = 5;
 
   assertThat(three, is(lessThanOrEqualTo(five)));
}

@Test
public void given5_whenLessThanOrEqualTo5_thenCorrect() {
   Integer five = 5;
 
   assertThat(five, is(lessThanOrEqualTo(five)));
}

Makes sense, right? Please note how simple is to understand what the predicates are asserting.

4.2. Order Matchers With String Values

Even though comparing numbers makes complete sense, many times it’s useful to compare other types of elements. That’s why order matchers can be applied to any class that implements the Comparable interface.

Let’s see some examples with Strings:

@Test
public void givenBenjamin_whenGreaterThanAmanda_thenCorrect() {
    String amanda = "Amanda";
    String benjamin = "Benjamin";
 
    assertThat(benjamin, is(greaterThan(amanda)));
}

@Test
public void givenAmanda_whenLessThanBenajmin_thenCorrect() {
    String amanda = "Amanda";
    String benjamin = "Benjamin";
 
    assertThat(amanda, is(lessThan(benjamin)));
}

String implements alphabetical order in compareTo method from the Comparable interface.

So, it makes sense that the word “Amanda” comes before the word “Benjamin”.

4.3. Order Matchers With LocalDate Values

Same as with Strings, we can compare dates. Let’s take a look at the same examples we created above but using LocalDate objects:

@Test
public void givenToday_whenGreaterThanYesterday_thenCorrect() {
    LocalDate today = LocalDate.now();
    LocalDate yesterday = today.minusDays(1);
 
    assertThat(today, is(greaterThan(yesterday)));
}

@Test
public void givenToday_whenLessThanTomorrow_thenCorrect() {
    LocalDate today = LocalDate.now();
    LocalDate tomorrow = today.plusDays(1);
    
    assertThat(today, is(lessThan(tomorrow)));
}

It’s very nice to see that the statement assertThat(today, is(lessThan(tomorrow))) is close to regular English.

4.4. Order Matchers With Custom Classes

So, why not create our own class and implement Comparable? That way, we can leverage order matchers to be used with custom order rules.

Let’s start by creating a Person bean:

public class Person {
    String name;
    int age;

    // standard constructor, getters and setters
}

Now, let’s implement Comparable:

public class Person implements Comparable<Person> {
        
    // ...

    @Override
    public int compareTo(Person o) {
        if (this.age == o.getAge()) return 0;
        if (this.age > o.getAge()) return 1;
        else return -1;
    }
}

Our compareTo implementation compares two people by their age. Let’s now create a couple of new tests:

@Test
public void givenAmanda_whenOlderThanBenjamin_thenCorrect() {
    Person amanda = new Person("Amanda", 20);
    Person benjamin = new Person("Benjamin", 18);
 
    assertThat(amanda, is(greaterThan(benjamin)));
}

@Test
public void 
givenBenjamin_whenYoungerThanAmanda_thenCorrect() {
    Person amanda = new Person("Amanda", 20);
    Person benjamin = new Person("Benjamin", 18);
 
    assertThat(benjamin, is(lessThan(amanda)));
}

Matchers will now work based on our compareTo logic.

5. NaN Matcher

Hamcrest provides one extra number matcher to define if a number is actually, not a number:

@Test
public void givenNaN_whenIsNotANumber_thenCorrect() {
    double zero = 0d;
    
    assertThat(zero / zero, is(notANumber()));
}

6. Conclusions

As you can see, number matchers are very useful to simplify common assertions.

What’s more, Hamcrest matchers in general, are self-explanatory and easy to read.

All this, plus the ability to combine matchers with custom comparison logic, make them a powerful tool for most projects out there.

The full implementation of the examples from this article can be found over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are closed on this article!