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 assertjassertj
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
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 setElement-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 failuresStatic 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-pattern | Why it fails | Fix |
|---|---|---|
Mix assertEquals and assertThat styles in same suite | Reader confusion; inconsistent failure messages | Pick one style and lint-enforce it |
usingRecursiveComparison without ignoringFields for volatile fields | Brittle: timestamps and generated IDs differ on every run | Exclude with ignoringFieldsMatchingRegexes(".*At", ".*Id") |
Skip assertAll() when using SoftAssertions instance | Failures are silently swallowed | Use assertSoftly() helper or always call assertAll() in a try-finally |
assertThat(flag).isEqualTo(true) instead of isTrue() | Loses semantic clarity in failure messages | Use isTrue() / isFalse() |
| Skip message check on exception assertions | Test passes for any exception of that type | Always add .hasMessageContaining(...) |
Limitations
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).