skip to content

AssertJ

AssertJ's assertThat entry point leads into type-aware chains like contains, hasSize, extracting and satisfies, with soft assertions for collecting multiple failures. Its detailed failure messages are the usual reason teams adopt it.

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

questions

6

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

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

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 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