skip to content

Assertion Libraries (AssertJ / Hamcrest)

The assertion libraries teams reach for beyond JUnit's built-ins: AssertJ's fluent chains and Hamcrest's composable matchers. Interviewers ask which you prefer and why, which is really a question about failure-message quality.

part ofJavaoverview, primer and where to startread it →
on this pageshow

explore

questions

11

What is AssertJ and how does its fluent assertThat(...) style differ from JUnit's assertEquals or Hamcrest's assertThat?

level: juniorimportance: must knowfreq 70%

answer

  1. assertThat(actual) first, then chain
  2. Type-specific assertion object -> IDE discoverability
  3. No expected/actual swap (unlike assertEquals)
  4. Chaining vs Hamcrest nested matchers
  5. Assertion layer only; still needs JUnit to run

basics

~20 s

AssertJ is a Java assertion library. You write assertThat(actual) then chain readable checks like .isEqualTo(expected) or .isNotNull(). It reads left-to-right (actual first) and gives clear failure messages, unlike assertEquals(expected, actual) where the argument order is easy to mix up.

solid answer

~40 s

AssertJ is a fluent assertion library for Java tests. You start with assertThat(actual), which returns a type-specific assertion object, then chain methods like isEqualTo, isNotNull, contains, hasSize. Because the actual value comes first and IDE autocompletion offers only assertions valid for that type, tests read like sentences and are discoverable. Compared to JUnit's assertEquals(expected, actual), AssertJ avoids the expected/actual argument-order confusion and produces richer, descriptive failure messages out of the box (e.g. showing the differing characters of a String). Compared to Hamcrest's assertThat(actual, matcher), AssertJ chains directly instead of nesting matcher objects, so there are no parentheses-heavy combinators and full IDE discoverability. It is the de-facto modern standard, often paired with JUnit 5 as the assertion layer.

code

java · 13 lines
java
import static org.assertj.core.api.Assertions.assertThat;

// AssertJ: actual first, chained, type-specific
assertThat(greet("Jo"))
    .isNotNull()
    .startsWith("Hello")
    .contains("Jo");

// vs JUnit classic (expected first - easy to swap)
// assertEquals("Hello, Jo", greet("Jo"));

// vs Hamcrest (nested matchers)
// assertThat(greet("Jo"), startsWith("Hello"));

go deeper

for a junior

Can write assertThat(actual).isEqualTo(expected) and explain that actual comes first and the message is readable.

for a middle

Contrasts AssertJ with JUnit and Hamcrest, knows the static import, and uses type-specific chained assertions for strings/collections.

for a senior

Articulates the discoverability and failure-message advantages, knows AssertJ is the assertion layer over a runner, and standardizes a team on it.

for a principal

Weighs library choice (AssertJ vs Hamcrest vs Truth) for the codebase, considers custom assertions, IDE/tooling consistency, and migration cost when setting test conventions.

## What an assertion library is A **unit test** verifies that code behaves as expected. Inside a test you compute an **actual** result and compare it to an **expected** value; an **assertion** is the statement that performs that comparison and fails the test (throws an `AssertionError`) if it does not hold. An **assertion library** provides those comparison methods. ## The three styles 1. **JUnit classic** — `assertEquals(expected, actual)`. The expected value comes *first*. This ordering is a classic source of confusion: swap the arguments and the failure message blames the wrong side. There is one method per kind of check, and complex checks (a collection contains an element) need extra code. 2. **Hamcrest** — `assertThat(actual, is(equalTo(expected)))`. Actual comes first (better), but you compose *matcher* objects (`is`, `equalTo`, `hasItem`, `containsInAnyOrder`) by nesting function calls. Powerful but parenthesis-heavy, and the IDE cannot easily tell you which matchers fit the value's type. 3. **AssertJ (fluent)** — `assertThat(actual).isEqualTo(expected)`. `assertThat(actual)` returns an **assertion object** whose available methods depend on the actual value's type. You then **chain** assertions with dots. ## Why fluent chaining helps - **Type-specific API.** `assertThat("hi")` returns a `StringAssert`, so the IDE offers `startsWith`, `containsIgnoringCase`, `isBlank`. `assertThat(List.of(1,2))` returns a `ListAssert`, offering `contains`, `hasSize`, `containsExactly`. This **discoverability** means you learn the API by typing a dot. - **Reads like a sentence.** `assertThat(user.getName()).isNotNull().startsWith("Jo")` reads naturally and chains multiple checks on one value. - **Actual first, always.** No expected/actual ambiguity. - **Rich failure messages.** When `isEqualTo` fails on two Strings, AssertJ prints both values and points at where they diverge; on objects it can show a field-by-field diff. Better messages mean faster debugging. ## The single entry point You statically import `org.assertj.core.api.Assertions.assertThat` (or `Assertions.*`). One overloaded `assertThat` method handles every type via overloads. The whole library is reached through this one name, which is why teams alias it as the modern default. ## Where it sits AssertJ is *only* the assertion layer. You still need a **test runner/framework** (JUnit 5, TestNG) to discover and run the test methods. AssertJ replaces JUnit's `Assertions.assertEquals`/`assertTrue`, not `@Test`. ## A first example ```java import static org.assertj.core.api.Assertions.assertThat; String name = greet("Jo"); assertThat(name) .isNotNull() .startsWith("Hello") .contains("Jo"); ``` If `name` were `null`, the message reads `Expecting actual not to be null` — precise and self-explanatory.

  • Does AssertJ replace JUnit?
    No. AssertJ replaces only the assertion calls (assertEquals/assertTrue). You still use JUnit (or TestNG) as the runner with @Test, lifecycle, and discovery. They are commonly used together.
  • Which import gives you the fluent assertThat?
    A static import of org.assertj.core.api.Assertions.assertThat (or Assertions.*). Beware: JUnit and Hamcrest also define assertThat, so importing the wrong one is a common mistake.

Like a chatbot that only offers buttons relevant to your last message: assertThat(value) hands you exactly the checks that make sense for that value's type, so you discover the API by clicking dots.

saying these in an interview costs you the question

  • Thinking AssertJ is a test runner that replaces JUnit's @Test
  • Claiming AssertJ uses expected-first ordering like assertEquals
  • Confusing AssertJ's chaining with Hamcrest's nested matcher composition
  • Statically importing the wrong assertThat (JUnit's or Hamcrest's)

context

open as a page

What is Hamcrest, and how does the assertThat(actual, matcher) style of assertion differ from a classic assertEquals(expected, actual)?

level: juniorimportance: must knowfreq 70%

basics

~10 s

Hamcrest is a library of reusable 'matcher' objects. Instead of assertEquals(2, x), you write assertThat(x, is(2)). The matcher describes what you expect, reads almost like English, and prints a clear message when it fails.

open as a page

How do you assert on collections and iterables with AssertJ (contains, containsExactly, containsExactlyInAnyOrder, hasSize, extracting)?

level: middleimportance: must knowfreq 65%

basics

~20 s

Call assertThat on the collection, then chain: hasSize(n) for count, contains(a, b) to check elements are present, containsExactly(...) for the exact contents in order, containsExactlyInAnyOrder(...) ignoring order, and extracting("field") to assert on one property of each element.

open as a page

How do you assert that code throws an exception in AssertJ using assertThatThrownBy and assertThatExceptionOfType?

level: middleimportance: must knowfreq 60%

basics

~10 s

Wrap the failing call in a lambda: assertThatThrownBy(() -> service.run()).isInstanceOf(IllegalArgumentException.class).hasMessageContaining("bad"). It runs the lambda, catches the thrown exception, and lets you assert on its type and message. assertThatExceptionOfType(X.class).isThrownBy(() -> ...) is an equivalent style.

open as a page

Walk through the common Hamcrest core matchers (is, equalTo, not, nullValue, containsString, hasItem) — what does each check and how do you read them?

level: juniorimportance: should knowfreq 60%

basics

~10 s

equalTo checks equality, is wraps a matcher to read nicer, not inverts, nullValue checks for null, containsString checks a substring, and hasItem checks a collection contains an element. You combine them like assertThat(list, hasItem(equalTo(3))).

open as a page

How do allOf and anyOf work, and why combine matchers instead of writing several separate assertThat calls?

level: middleimportance: should knowfreq 48%

basics

~20 s

allOf(a, b, c) passes only if all the matchers pass (logical AND); anyOf(a, b, c) passes if at least one passes (logical OR). Combining them lets one assertThat check several conditions on one value and report them together.

open as a page

How do extracting and satisfies let you assert on derived values and custom conditions, and when do you reach for usingRecursiveComparison?

level: seniorimportance: should knowfreq 40%

basics

~20 s

extracting pulls a field (or several) out of an object or each element so you assert on just that. satisfies(obj -> { assertThat(obj.x)... }) lets you run arbitrary assertions inside a lambda for custom checks. usingRecursiveComparison().isEqualTo(expected) compares two objects field-by-field deeply, without needing equals() to be implemented.

open as a page

What are AssertJ soft assertions, when do you use them, and what is the gotcha that makes failures disappear?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Normally the first failed assertion throws and stops the test, hiding later problems. Soft assertions collect all failures and report them together. You make assertions through a SoftAssertions object, then call assertAll() at the end. The gotcha: if you forget assertAll(), every failure is silently swallowed and the test passes.

open as a page

What makes Hamcrest produce its readable mismatch messages? Explain the roles of matches, describeTo, and describeMismatch.

level: seniorimportance: should knowfreq 40%

basics

~20 s

Each matcher implements matches (does the value pass?), describeTo (write what was expected), and describeMismatch (write what actually happened). assertThat calls matches; if it's false it asks the matcher to describe both sides, giving the 'Expected: ... but: was ...' message.

open as a page

What makes AssertJ's failure messages useful, and how do as()/describedAs() and custom assertions improve diagnosability?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

AssertJ failure messages show the actual value, the expected value, and often where they differ, so you understand a failure without rerunning in a debugger. You can add context with as("description") before the assertion, and for domain types you can write custom assertion classes so messages speak your domain's language.

open as a page

How do you write a custom Hamcrest matcher, and when is it worth doing over a plain assertion or allOf?

level: seniorimportance: nice to knowfreq 33%

basics

~10 s

Extend TypeSafeMatcher<T>, implement matchesSafely (the check) and describeTo (the expected message), and expose a static factory method returning Matcher<T>. Write one when a check is reused a lot or needs a domain-specific failure message.

open as a page