Testland
Browse all skills & agents

assertj

Reference for AssertJ - the canonical JVM fluent-assertion library pairable with JUnit 5 / TestNG / Spock; covers the assertThat() entry point, collection matchers (contains, containsExactly, allSatisfy, extracting), exception assertions (assertThatThrownBy, catchThrowable), SoftAssertions for multi-failure collection, recursive comparison (usingRecursiveComparison) for deep equality, and domain-specific custom assertions via AbstractAssert. Use when writing JVM tests that need richer failure messages than built-in assertEquals, or when verifying complex object graphs, exception types, or collections.

Install with skills.sh (any agent)

npx skills add testland/qa --skill assertj
View source

assertj

Overview

AssertJ is "a Java library that provides a rich set of assertions and truly helpful error messages, improves test code readability, and is designed to be super easy to use within your favorite IDE" (assertj.github.io/doc (opens in new window)). It ships modules for JDK core types, Guava, Joda-Time, and databases; this skill covers assertj-core (JDK types).

Works alongside junit5-tests, testng-tests, or spock-tests. AssertJ handles the assertion layer; those skills handle the test runner.

This skill is a reference - defines the matcher catalog and patterns; does not run tests. Advanced per-feature variants live in references/matcher-catalog.md; provenance links are consolidated under References.

When to use

  • JVM project (Java 8+ or Kotlin 1.9+) using any test framework.
  • Need richer assertion failure messages than built-in assertEquals / assertTrue.
  • Deep-equality checking between object graphs without overriding equals.
  • Verifying collections, exception details, or running multiple assertions without failing on the first one.

Step 1 - Install

Maven:

<dependency>
  <groupId>org.assertj</groupId>
  <artifactId>assertj-core</artifactId>
  <version>3.27.7</version>
  <scope>test</scope>
</dependency>

Gradle:

testImplementation("org.assertj:assertj-core:3.27.7")

The 3.27.7 version is current at time of writing; check Maven Central for the latest assertj-core release before copying.

Static import the entry class once per test file:

import static org.assertj.core.api.Assertions.*;

Step 2 - assertThat entry point

assertThat(actual) returns a type-specific assertion object. All assertions chain fluently from it.

assertThat(frodo.getName()).isEqualTo("Frodo");

assertThat("text").isNotNull()
                  .startsWith("te")
                  .contains("ex");

Common predicates available on every assertion:

assertThat(value).isEqualTo(expected);
assertThat(value).isNotEqualTo(other);
assertThat(value).isNull();
assertThat(value).isNotNull();
assertThat(value).isSameAs(ref);            // reference equality
assertThat(value).isInstanceOf(MyClass.class);
assertThat(flag).isTrue();
assertThat(flag).isFalse();

Use .as("description") to label an assertion in failure output:

assertThat(user.getAge()).as("user age").isGreaterThan(0);

Step 3 - Collection assertions

assertThat(list).hasSize(9);
assertThat(list).contains(frodo, sam);                // any order, subset
assertThat(list).containsExactly(frodo, sam, pippin); // exact order, exact set

Element-level verification:

assertThat(hobbits).allSatisfy(c -> {
    assertThat(c.getRace()).isEqualTo(HOBBIT);
    assertThat(c.getName()).isNotEqualTo("Sauron");
});

Extraction:

// Extract a property then assert on extracted values
assertThat(fellowship).extracting("name")
                      .contains("Boromir", "Gandalf", "Frodo");

containsOnly / doesNotContain / isEmpty, multi-property tuple extraction, filteredOn, and anySatisfy are in references/matcher-catalog.md.

Step 4 - Exception assertions

Primary form - assertThatThrownBy:

assertThatThrownBy(() -> parser.parse(null))
    .isInstanceOf(IllegalArgumentException.class)
    .hasMessageContaining("null input");

Type-first (assertThatExceptionOfType), BDD capture (catchThrowable / catchThrowableOfType), cause-chain inspection, and no-exception (assertThatCode) forms are in references/matcher-catalog.md.

Step 5 - SoftAssertions

SoftAssertions collect failures instead of stopping at the first one. All violations are reported together in a single error.

Instance form:

SoftAssertions softly = new SoftAssertions();
softly.assertThat(actual.getName()).isEqualTo("Frodo");
softly.assertThat(actual.getAge()).isEqualTo(33);
softly.assertThat(actual.getRace()).isEqualTo(HOBBIT);
softly.assertAll(); // throws one error listing all failures

Static helper - assertSoftly: manages lifecycle automatically; assertAll() is called on exit:

assertSoftly(softly -> {
    softly.assertThat(frodo.getName()).isEqualTo("Frodo");
    softly.assertThat(frodo.getAge()).isEqualTo(33);
    softly.assertThat(frodo.getRace()).isEqualTo(HOBBIT);
});

Use SoftAssertions when a test covers multiple independent properties of the same subject, so a single run reveals all mismatches rather than stopping at the first.

Step 6 - Recursive comparison

usingRecursiveComparison() compares object graphs field-by-field without requiring equals overrides.

assertThat(sherlock).usingRecursiveComparison()
                    .isEqualTo(sherlockClone);

Exclude fields:

assertThat(actual).usingRecursiveComparison()
                  .ignoringFields("id", "home.address.street")
                  .isEqualTo(expected);

Regex field exclusion, ignoringActualNullFields, per-type comparators, and withStrictTypeChecking are in references/matcher-catalog.md.

Step 7 - Custom assertions

Extend AbstractAssert with type parameters <SELF, ACTUAL> (SELF is the concrete assertion class, for chaining), then expose a static factory mirroring assertThat:

public class PersonAssert extends AbstractAssert<PersonAssert, Person> {
    public PersonAssert(Person actual) { super(actual, PersonAssert.class); }

    public PersonAssert hasName(String name) {
        isNotNull();
        if (!actual.getName().equals(name)) {
            failWithMessage("Expected name <%s> but was <%s>", name, actual.getName());
        }
        return this;
    }
}

public static PersonAssert assertThat(Person actual) {
    return new PersonAssert(actual);
}

Usage then reads like built-in assertions: assertThat(person).hasName("Alice"). A multi-method example is in references/matcher-catalog.md.

Example - full test method

@Test
void order_ships_to_correct_address() {
    Order order = orderService.place(items, address);

    assertThat(order).isNotNull();
    assertThat(order.getItems()).hasSize(2)
                                .extracting("sku")
                                .containsExactly("ITEM-001", "ITEM-002");
    assertThat(order).usingRecursiveComparison()
                     .ignoringFields("id", "createdAt")
                     .isEqualTo(expectedOrder);
}

@Test
void invalid_quantity_throws() {
    assertThatThrownBy(() -> orderService.place(emptyItems, address))
        .isInstanceOf(IllegalArgumentException.class)
        .hasMessageContaining("at least one item");
}

@Test
void order_summary_fields_all_valid() {
    OrderSummary summary = orderService.summarize(orderId);

    assertSoftly(softly -> {
        softly.assertThat(summary.getTotal()).isGreaterThan(BigDecimal.ZERO);
        softly.assertThat(summary.getItemCount()).isEqualTo(2);
        softly.assertThat(summary.getStatus()).isEqualTo(OrderStatus.CONFIRMED);
    });
}

Anti-patterns

Anti-patternWhy it failsFix
Mix assertEquals and assertThat styles in same suiteReader confusion; inconsistent failure messagesPick one style and lint-enforce it
usingRecursiveComparison without ignoringFields for volatile fieldsBrittle: timestamps and generated IDs differ on every runExclude with ignoringFieldsMatchingRegexes(".*At", ".*Id")
Skip assertAll() when using SoftAssertions instanceFailures are silently swallowedUse assertSoftly() helper or always call assertAll() in a try-finally
assertThat(flag).isEqualTo(true) instead of isTrue()Loses semantic clarity in failure messagesUse isTrue() / isFalse()
Skip message check on exception assertionsTest passes for any exception of that typeAlways add .hasMessageContaining(...)

Limitations

  • assertj-core targets Java 8+; Kotlin works but the API is more ergonomic in Java (Kotlin users may prefer AssertK).
  • usingRecursiveComparison on cyclic object graphs requires .withCyclicSafeComparison(); defaults to withIgnoreAllOverriddenEquals() which skips equals overrides.
  • SoftAssertions does not propagate assertion context automatically to nested lambdas; each lambda needs its own softly.assertThat(...) call.
  • The extracting overload using string property names uses reflection; use the Function-based overload for compile-time safety.

References

AssertJ - matcher catalog (advanced variants)

View source (opens in new window)

AssertJ - matcher catalog (advanced variants)

Advanced per-feature variants that build on the core forms in the assertj SKILL.md. Each section links its AssertJ docs source.

Collections and iterables

Source: https://assertj.github.io/doc/#collection-assertions

assertThat(list).isEmpty();
assertThat(list).containsOnly(frodo, sam);   // any order, exact set
assertThat(list).doesNotContain(sauron);

// Extract multiple properties as tuples
assertThat(fellowship).extracting("name", "age")
                      .contains(tuple("Boromir", 37), tuple("Sam", 38));

// Filter before asserting
assertThat(fellowship).filteredOn(c -> c.getName().contains("o"))
                      .containsOnly(aragorn, frodo);

// Assert at least one element matches
assertThat(hobbits).anySatisfy(c ->
    assertThat(c.getName()).isEqualTo("Sam"));

Exception assertions

Source: https://assertj.github.io/doc/#exception-assertions

Type-first form - assertThatExceptionOfType:

assertThatExceptionOfType(IOException.class)
    .isThrownBy(() -> { throw new IOException("boom!"); })
    .withMessage("%s!", "boom")
    .withNoCause();

BDD form - catchThrowable: separates the WHEN step from THEN:

// WHEN
Throwable thrown = catchThrowable(() -> names[9]);
// THEN
assertThat(thrown).isInstanceOf(ArrayIndexOutOfBoundsException.class)
                  .hasMessageContaining("9");

Typed capture - catchThrowableOfType: returns the concrete exception for further assertion:

TextException ex = catchThrowableOfType(TextException.class,
    () -> { throw new TextException("boom!", 1, 5); });
assertThat(ex.line).isEqualTo(1);

Cause chain inspection:

assertThat(thrown).hasCauseInstanceOf(NullPointerException.class);
assertThat(thrown).hasRootCauseInstanceOf(SocketException.class);
assertThat(thrown).cause().hasMessage("underlying cause");

Assert no exception:

assertThatCode(() -> service.process(input)).doesNotThrowAnyException();

Recursive comparison

Source: https://assertj.github.io/doc/#recursive-comparison

Exclude by regex pattern:

assertThat(actual).usingRecursiveComparison()
                  .ignoringFieldsMatchingRegexes(".*At", ".*Id")
                  .isEqualTo(expected);

Ignore nulls in actual:

assertThat(partial).usingRecursiveComparison()
                   .ignoringActualNullFields()
                   .isEqualTo(expected);

Custom comparator per type:

BiPredicate<Double, Double> closeEnough = (d1, d2) -> Math.abs(d1 - d2) <= 0.5;
assertThat(frodo).usingRecursiveComparison()
                 .withEqualsForType(closeEnough, Double.class)
                 .isEqualTo(tallerFrodo);

Strict type checking:

assertThat(actual).usingRecursiveComparison()
                  .withStrictTypeChecking()
                  .isEqualTo(expected);

Custom assertions

Source: https://assertj.github.io/doc/#custom-assertions

Extend AbstractAssert<SELF, ACTUAL> with one method per domain rule, then expose a static factory mirroring assertThat:

public class PersonAssert extends AbstractAssert<PersonAssert, Person> {

    public PersonAssert(Person actual) {
        super(actual, PersonAssert.class);
    }

    public PersonAssert hasName(String name) {
        isNotNull();
        if (!actual.getName().equals(name)) {
            failWithMessage("Expected name <%s> but was <%s>",
                            name, actual.getName());
        }
        return this;
    }

    public PersonAssert isAdult() {
        if (actual.getAge() < 18) {
            failWithMessage("Expected person to be an adult");
        }
        return this;
    }
}

public static PersonAssert assertThat(Person actual) {
    return new PersonAssert(actual);
}

Usage then reads like built-in assertions:

assertThat(person).hasName("Alice").isAdult();

Related skills

junit5-tests

Configures and runs JUnit 5 (Jupiter) - modern JVM testing platform with annotations (`@Test` / `@ParameterizedTest` / `@RepeatedTest` / `@TestFactory`), lifecycle hooks (`@BeforeAll` / `@BeforeEach` / `@AfterEach` / `@AfterAll`), extension model (`@ExtendWith`), display names (`@DisplayName`), conditional execution (`@EnabledOnOs`, `@EnabledIf`), parallel execution config; integrates with Maven Surefire / Gradle test task / IntelliJ. Use when the user works with Java / Kotlin codebases needing the modern JVM standard.

kotest-tests

Configures and runs Kotest - Kotlin-native test framework with multiple specification styles (StringSpec, FunSpec, BehaviorSpec, DescribeSpec, ShouldSpec, FreeSpec, FeatureSpec, ExpectSpec, AnnotationSpec); rich matcher library; built-in property-based testing (alternative to jqwik); coroutines support; data-driven testing; isolation modes per-spec or per-test; integrates with Gradle JVM test task. Use when working with Kotlin and wanting Kotlin-idiomatic DSL over JUnit 5's annotation-driven approach. Matchers (shouldBe, shouldContain) are bundled with the runner and are not a drop-in replacement for a standalone JVM assertion library; for assertion-only use paired with JUnit 5 / TestNG / Spock see assertj.

scalatest

Configures and runs ScalaTest - Scala-native test framework with multiple specification styles (FlatSpec, FunSuite, WordSpec, FreeSpec, AsyncFlatSpec for async); Matchers DSL (`should equal`, `should contain`, `shouldBe a [Class]`); integrates with ScalaCheck for property-based testing; sbt + Maven + Gradle support; tagged-test selective execution. Use when working with Scala codebases.

spock-tests

Configures and runs Spock - Groovy-based JVM testing framework with given/when/then BDD blocks, where: data tables for parametrized tests, built-in mocking via Mock()/Stub()/Spy(), interaction-based testing (verify method calls in declarative DSL), implicit assertions in then: blocks. Use when working with Java/Kotlin codebases that benefit from Groovy DSL expressiveness, or maintaining existing Spock projects.

testng-tests

Configures and runs TestNG - JVM testing framework with `@Test` priorities + groups + `dependsOnMethods`; `@DataProvider` for parametrized tests with method-level data sources; `testng.xml` suite definitions for grouping + parallelism config; listeners (`ITestListener`, `ISuiteListener`) for hooks; `ITestContext` for cross-test state; integrates with Maven Surefire / Gradle. Use when working with legacy TestNG codebases or needing TestNG-specific features (test method dependencies, suite-level XML config).