skip to content

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%

answer

  1. extracting = project to chosen field(s), method ref is refactor-safe
  2. satisfies(lambda) = arbitrary/grouped assertions; satisfiesAnyOf = OR of groups
  3. allSatisfy/anySatisfy/noneSatisfy on collections
  4. usingRecursiveComparison().isEqualTo() = deep field compare, ignores equals()
  5. ignoringFields("id","createdAt") to skip volatile fields

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.

solid answer

~40 s

These three handle 'assert on derived or structured state'. extracting maps an object (or each element of a collection) to one or more properties so you assert on exactly those: assertThat(user).extracting(User::getName, User::getRole). satisfies takes a Consumer in which you write any assertions on the value, perfect for grouped or custom logic that no built-in method expresses; satisfiesAnyOf passes if any one block passes. usingRecursiveComparison().isEqualTo(expected) compares two objects by walking all fields recursively, ignoring whether equals/hashCode are overridden; you tune it with ignoringFields, ignoringFieldsOfTypes, comparingOnlyFields, withEqualsForType, and tolerance for doubles. Use extracting when you care about a subset of fields, satisfies for ad-hoc/grouped assertions and conditions, and usingRecursiveComparison to compare whole objects/DTOs structurally (e.g. expected vs actual response) without writing or trusting equals. They compose with collections and soft assertions and produce field-aware failure messages.

code

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

// extracting a subset of fields
assertThat(user)
    .extracting(User::getName, User::getRole)
    .containsExactly("Ann", "ADMIN");

// satisfies: grouped/custom assertions
assertThat(user).satisfies(u -> {
    assertThat(u.getName()).startsWith("A");
    assertThat(u.getAge()).isBetween(18, 65);
});

// deep structural equality without relying on equals()
assertThat(actualDto)
    .usingRecursiveComparison()
    .ignoringFields("id", "createdAt")
    .isEqualTo(expectedDto);

go deeper

for a junior

Can use extracting to pull one field and assert on it.

for a middle

Uses extracting with multiple fields and satisfies for grouped custom assertions; knows allSatisfy/anySatisfy.

for a senior

Reaches for usingRecursiveComparison with ignoringFields to compare DTOs/graphs structurally without equals, and chooses extracting vs satisfies vs recursive comparison appropriately.

for a principal

Defines conventions for object comparison in tests (recursive comparison vs maintained equals), manages ignored-field policies for ids/timestamps, and builds reusable Consumer requirements/Conditions for the domain.

## The shared problem You frequently need to assert on something **derived** from an object — a field, a transformation, a deep structural match — rather than the object as a whole. AssertJ gives three complementary tools. ## extracting — project to specific fields `extracting` returns an assertion over a **derived value**: - On a **single object**: `assertThat(user).extracting(User::getName).isEqualTo("Ann")`, or multiple fields returning a list/tuple: `assertThat(user).extracting(User::getName, User::getRole).containsExactly("Ann", "ADMIN")`. - On a **collection** (covered in the collections leaf): it maps each element. - By **method reference** (type-safe, refactor-proof) or **property/field name string** (reflection-based, not refactor-safe). Use it when only a **subset** of state matters, keeping the test focused and the failure message pointed at that field. ## satisfies — run arbitrary assertions in a lambda Some checks have no dedicated method, or you want to **group** several related checks on a value, or assert on a single extracted element. `satisfies` takes a `Consumer<T>`: ```java assertThat(user).satisfies(u -> { assertThat(u.getName()).startsWith("A"); assertThat(u.getAge()).isBetween(18, 65); }); ``` Everything inside is normal AssertJ; the block fails if any inner assertion fails. Variants: - `satisfiesAnyOf(block1, block2)` — passes if **at least one** block fully passes (logical OR of assertion groups). - On collections, `allSatisfy`, `anySatisfy`, `noneSatisfy`, `zipSatisfy` apply a block to elements. `satisfies` is also the idiomatic way to make a reusable **requirement** (a named `Consumer`) you apply in many tests. ## Conditions (related) `Condition<T>` plus `is(condition)` / `has(condition)` / `areAtLeast(...)` express named predicates with good messages; `satisfies` covers most needs more simply. ## usingRecursiveComparison — deep field-by-field equality Comparing two objects with `isEqualTo` relies on their `equals()`. For DTOs/entities without a (correct) `equals`, that is reference identity and fails. `usingRecursiveComparison()` walks **all fields recursively** and compares values, **ignoring** `equals`/`hashCode`: ```java assertThat(actual) .usingRecursiveComparison() .ignoringFields("id", "createdAt") .isEqualTo(expected); ``` Tuning knobs: `ignoringFields(...)`, `ignoringFieldsOfTypes(...)`, `ignoringFieldsMatchingRegexes(...)`, `comparingOnlyFields(...)`, `ignoringActualNullFields()`, `withComparatorForType(...)` / `withEqualsForType(...)`, and numeric tolerance via `withComparatorForType` on doubles. `usingRecursiveFieldByFieldElementComparator()` does the same per element in a collection. ### When to choose which - **extracting** — you care about one or a few fields; want a focused, refactor-safe check. - **satisfies** — custom or grouped assertions, OR-of-groups, reusable requirements, asserting on a single element. - **usingRecursiveComparison** — compare whole objects/graphs structurally (expected vs actual builder output, mapper result, API response) **without** writing or trusting `equals`, while excluding volatile fields like ids/timestamps. All three compose with soft assertions and collection assertions and yield field-level failure messages, which is the real payoff over hand-rolled `assertThat(a.getX()).isEqualTo(b.getX())` chains.

  • Why use usingRecursiveComparison instead of isEqualTo for two DTOs?
    isEqualTo delegates to the object's equals(); if equals is not overridden it is reference identity and two distinct-but-equal DTOs fail. usingRecursiveComparison compares all fields recursively by value, ignoring equals/hashCode, and lets you ignore volatile fields like id or createdAt, so you assert structural equality without writing or trusting equals.
  • What does satisfiesAnyOf do?
    It accepts several assertion blocks (Consumers) and passes if at least one block fully succeeds — a logical OR over groups of assertions. Useful when a value may legitimately be in one of several valid shapes.

saying these in an interview costs you the question

  • Using isEqualTo on DTOs without equals() and expecting structural comparison
  • Forgetting usingRecursiveComparison ignores equals/hashCode entirely
  • Putting plain code (not assertions) in satisfies and expecting it to fail the test
  • Using string-name extracting in refactor-heavy code instead of method references

context