Skip to main content

Hamcrest

Hamcrest is a framework for writing matcher objects that can be combined to create flexible expressions of intent. It was popular with JUnit 4, but has several drawbacks compared to modern assertion libraries like AssertJ. The matcher-based approach requires many static imports, has less discoverable APIs, and can be confusing to use correctly.

Verbose matcher composition

Hamcrest requires composing matchers with is(), not(), and nullValue() to express simple assertions. This leads to verbose, nested calls that are harder to read than fluent assertions.

HamcrestTest.java
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;

class HamcrestTest {

@Test
void bundle() {
List<Book> books = new Bundle().getBooks();

assertThat(books, is(not(nullValue())));
assertThat(books, hasSize(3));
assertThat(books, hasItem(new Book("Effective Java", "Joshua Bloch", 2001)));
assertThat(books, hasItem(new Book("Java Concurrency in Practice", "Brian Goetz", 2006)));
assertThat(books, hasItem(new Book("Clean Code", "Robert C. Martin", 2008)));
assertThat(books, not(hasItem(new Book("Java 8 in Action", "Raoul-Gabriel Urma", 2014))));
}
}
warning

Hamcrest requires numerous static imports and verbose matcher composition like is(not(nullValue())). Each assertion is a separate statement, making the test longer and harder to maintain.

Confusing matcher semantics

Hamcrest matchers like contains() and hasItem() have subtle differences that are easy to get wrong. contains() checks for exact order and completeness, while hasItem() checks for presence of a single item.

HamcrestTest.java
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;

class HamcrestTest {

@Test
void failingTest() {
List<Book> books = new Bundle().getBooks();

// Wrong: Each contains() checks for the ENTIRE list content in order
assertThat(books, contains(new Book("Effective Java", "Joshua Bloch", 2001)));
assertThat(books, contains(new Book("Java Concurrency in Practice", "Brian Goetz", 2006)));
assertThat(books, contains(new Book("Clean Code", "Robert C. Martin", 2008)));

// Correct: All items must be in a single contains() call
assertThat(books, contains(
new Book("Effective Java", "Joshua Bloch", 2001),
new Book("Java Concurrency in Practice", "Brian Goetz", 2006),
new Book("Clean Code", "Robert C. Martin", 2008)
));
}
}
danger

The contains() matcher checks that the collection contains exactly the specified items in order. Using it with a single item per assertion will fail because it expects the entire list to be just that one item. This is confusing and error-prone.

Poor discoverability

Hamcrest's matcher-based approach requires knowing which static imports to use and how to compose them. Without IDE autocomplete support, it's difficult to discover what matchers are available and how to use them.

HamcrestTest.java
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;

class HamcrestTest {
// You need to know which matchers exist and import them manually
// IDE autocomplete doesn't help much after assertThat(value, ...)
}
warning

Hamcrest requires memorizing matcher names and manually adding static imports. The separation between the value and the matcher makes IDE autocomplete less helpful.