Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / DevOps / testing

Writing Expressive Unit Tests using Hamcrest

5.00/5 (5 votes)
29 Sep 2014CPOL3 min read 13K  
A quick introduction to Hamcrest framework for writing expressive unit-test cases for Java applications.

Introduction

Unit test cases are an important and integral part of every application. Unit test cases help to ensure that the code works as expected. Any deviation from the expected behavior is caught before the source code reaches production environment. This article provides a quick introduction about using Hamcrest framework to write unit tests which are readable, maintainable.

Background

In the Java world, there have been many unit-testing frameworks, Junit and TestNG being the most popular. It's been a general trend that as and when the size and complexity of test cases grow, the unit test cases start becoming unreadable and in-expressive. This is where Hamcrest can help, write expressive unit test cases.

Core Features of Hamcrest

Improves Readability of Test Cases

The dialect/selection of method names in Hamcrest framework improves readability of test cases.

Following are some of the examples:

Java
// Clearly helps to understand that if actual value is equal to expectedValue
 
assertThat(actualValue, is(equalTo(expectedValue)));

// Employee object has a property named "firstName"

assertThat(employee, hasProperty("firstName"));
    
// Marital status should be a value of any of these possible values.

assertThat(employee.getMaritalStatus(), isIn(new String[]{"Married", "Single - Never Married", "Widow", "Divorced"}));

Enforces Type Safety

Hamcrest method doesn't allow to compare apples with oranges, rather it enforces comparing apples with apples. This helps a lot while writing test cases as we won't be comparing the types that don't match. The first level of check is to ensure that we are comparing same objects. Hamcrest heavily uses Java generics to ensure type safety.

Some of the examples are as follows:

Java
// Generates compile-time error; isManager returns boolean whereas we are comparing with String.

assertThat(employee.isManager(), is("true"));

Supports Chaining of Conditions

Hamcrest allows to write multiple conditions in single assertThat statement. Multiple conditions can be grouped using logical conditions like or/and. We can write test cases where a condition can must match all the conditions or must match anyone of the condition. Some of the examples are as follows:

Java
// Passes only allOf the condition are true; i.e. the firstName is not null, and equal to John
    
assertThat(employee.getFirstName(), allOf(notNullValue(), is("John")));   

Supports Rich-set of Matchers

Hamcrest supports good number of Matchers already available, some of them are listed below:

Matcher Name Description
allOf Creates a matcher that matches if examined object matches ALL of the specified matchers
anyOf Creates a matcher that matches if the examined object matches ANY of the specified matchers
is A shortcut to frequently used is(equalTo(x))
equalTo Creates a matcher that matches when the examined object is logically equal to the specified operand, by calling the Object.equals
not Creates a matcher that wraps an existing matcher, but inverts the logic by which it will match
notNullValue Creates a matcher that matches if examined object is not null
nullValue Creates a matcher that matches if the examined object is null
hasProperty Creates a matcher that matches when the examined object has a JavaBean property with the specified name
startsWith Creates a matcher that matches if the examined String starts with the specified String
endsWith Creates a matcher that matches if the String ends with specified String

Highly Extensible

Hamcrest not only provides its own rich feature set but it fully supports creation of custom Matchers. In order to write custom matcher, we need to extend from TypeSafeMatcher base class.

Following is a simple example taken from Hamcrest documentation:

Java
//Custom matcher
public class IsNotANumber extends TypeSafeMatcher<Double> {

   // Actual comparison. If the number is NaN that means its not a number
   @Override
   public boolean matchesSafely(Double number) {
    return number.isNaN();
   }
    
   // Adds custom description.    
   public void describeTo(Description description) {
    description.appendText("This is not a number");
   }

   // Actual method that is called from the codebase. 
   @Factory
   public static <T> Matcher<Double> notANumber() {
    return new IsNotANumber();
   }
}

The above created custom matcher can be used as:

Java
// The notANumber is a static method available for usage
assertThat(1.0, is(notANumber()));

As we can see, writing test cases using the Hamcrest constructs improves the readability of the test cases making them easy to understand and maintain.

Maven Dependency

The following Maven dependencies need to be added to pom.xml to use all the features of Hamcrest.

XML
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
    </dependency>
    <dependency>
        <groupId>org.hamcrest</groupId>
        <artifactId>hamcrest-all</artifactId>
        <version>1.3</version>
    </dependency>
</dependencies>

Sample Test Case

Java
import com.test.matchers.Employee;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;

public class EmployeeTest {
    private Employee employee;    
    
    @Before
    public void initEmployee() {
        employee = new Employee();
        employee.setFirstName("John");
        employee.setDesignation("Manager");
        employee.setAge(30);
    }
    
    @Test
    public void firstNameCannotBeNull() {
        assertThat("", employee.getFirstName(), notNullValue());                
    }
    
    @Test
    public void firstNameEqualsJohn() {
        assertThat("", employee.getFirstName(), is("John"));
    }
    
    @Test
    public void designationShouldBeManagerOrContractor() {
        assertThat(employee.getDesignation(), anyOf(is("Manager"), is("Contractor")));
    }
    
    @Test
    public void employeeShouldBeOlderThan20() {
        assertThat(employee.getAge(), greaterThan(20L));
    }
    
    
    @Test
    public void employeeHasAgeProperty() {
        assertThat(employee, hasProperty("age"));
    }
}

References

History

  • 29th September, 2014: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)