Contents
- Dependency
- Basic Assertions
- String Assertions
- Collection Assertions
- Object & Field Assertions
- Exception Assertions
- Soft Assertions
- Custom Assertions
AssertJ Core is included in spring-boot-starter-test. For standalone projects:
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.26.0</version>
<scope>test</scope>
</dependency>
All assertions start with a static import: import static org.assertj.core.api.Assertions.*;
The class below shows the implementation. Key points are highlighted in the inline comments.
import static org.assertj.core.api.Assertions.*;
// Equality
assertThat(42).isEqualTo(42);
assertThat("hello").isNotEqualTo("world");
// Null checks
assertThat(user).isNotNull();
assertThat(result).isNull();
// Boolean
assertThat(list.isEmpty()).isTrue();
assertThat(service.isActive()).isFalse();
// Type check
assertThat(obj).isInstanceOf(String.class);
// Comparable
assertThat(score).isGreaterThan(0)
.isLessThanOrEqualTo(100)
.isBetween(1, 99);
// Same reference
assertThat(actual).isSameAs(expected);
The class below shows the implementation. Key points are highlighted in the inline comments.
String email = "alice@example.com";
assertThat(email)
.isNotEmpty()
.isNotBlank()
.startsWith("alice")
.endsWith(".com")
.contains("@")
.doesNotContain("bob")
.matches("^[\\w.]+@[\\w.]+\\.[a-z]{2,}$")
.hasSize(17)
.isEqualToIgnoringCase("ALICE@EXAMPLE.COM");
The class below shows the implementation. Key points are highlighted in the inline comments.
List<String> fruits = List.of("apple", "banana", "cherry");
assertThat(fruits)
.hasSize(3)
.isNotEmpty()
.contains("banana")
.containsExactly("apple", "banana", "cherry") // exact order
.containsExactlyInAnyOrder("cherry", "apple", "banana")
.doesNotContain("durian")
.allMatch(f -> f.length() > 3)
.anyMatch(f -> f.startsWith("a"))
.noneMatch(f -> f.isBlank());
// Extracting fields from a list of objects
List<User> users = List.of(
new User(1L, "Alice", "admin"),
new User(2L, "Bob", "user")
);
assertThat(users)
.extracting(User::name)
.containsExactlyInAnyOrder("Alice", "Bob");
assertThat(users)
.extracting("name", "role")
.containsExactly(
tuple("Alice", "admin"),
tuple("Bob", "user")
);
// Map assertions
Map<String, Integer> scores = Map.of("Alice", 95, "Bob", 80);
assertThat(scores)
.containsKey("Alice")
.containsEntry("Bob", 80)
.doesNotContainKey("Charlie")
.hasSize(2);
Use usingRecursiveComparison() for deep field-by-field equality without requiring equals(), and extracting() to pluck specific fields for assertion.
Order expected = new Order("o1", "Widget", 3, OrderStatus.PENDING);
Order actual = orderService.place("Widget", 3);
// Deep recursive comparison — ignores equals()
assertThat(actual)
.usingRecursiveComparison()
.ignoringFields("id", "createdAt") // skip generated fields
.isEqualTo(expected);
// Extracting specific fields
assertThat(actual)
.extracting(Order::product, Order::quantity)
.containsExactly("Widget", 3);
// Conditions
assertThat(actual.status()).satisfies(status -> {
assertThat(status).isNotNull();
assertThat(status.name()).isIn("PENDING", "PROCESSING");
});
The class below shows the implementation. Key points are highlighted in the inline comments.
// Assert type and message
assertThatThrownBy(() -> userService.findById(-1L))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("must be positive")
.hasMessageStartingWith("User ID");
// Assert a specific exception type with typed result
assertThatExceptionOfType(EntityNotFoundException.class)
.isThrownBy(() -> userService.findById(999L))
.withMessage("User 999 not found")
.withNoCause();
// Assert no exception is thrown
assertThatNoException()
.isThrownBy(() -> userService.findById(1L));
// assertThatCode is another common form
assertThatCode(() -> configService.reload())
.doesNotThrowAnyException();
Normal assertions stop at the first failure. SoftAssertions collects all failures and reports them together — useful when you need to validate a full object in one test pass.
import org.assertj.core.api.SoftAssertions;
@Test
void validatesUserProfile() {
User user = userService.loadProfile(1L);
SoftAssertions softly = new SoftAssertions();
softly.assertThat(user.name()).isEqualTo("Alice");
softly.assertThat(user.email()).contains("@");
softly.assertThat(user.age()).isBetween(18, 120);
softly.assertThat(user.role()).isIn("admin", "user", "viewer");
softly.assertAll(); // throws AssertionError listing ALL failures
}
// Alternatively use the lambda form which calls assertAll automatically
@Test
void validatesOrderSoftly() {
Order order = orderService.place("Widget", 5);
SoftAssertions.assertSoftly(softly -> {
softly.assertThat(order.id()).isNotNull();
softly.assertThat(order.product()).isEqualTo("Widget");
softly.assertThat(order.quantity()).isEqualTo(5);
softly.assertThat(order.status()).isEqualTo(OrderStatus.PENDING);
});
}
Extend AbstractAssert to create domain-specific assertion classes that read naturally and produce clear failure messages.
import org.assertj.core.api.AbstractAssert;
public class OrderAssert extends AbstractAssert<OrderAssert, Order> {
public OrderAssert(Order order) {
super(order, OrderAssert.class);
}
public static OrderAssert assertThat(Order order) {
return new OrderAssert(order);
}
public OrderAssert hasProduct(String product) {
isNotNull();
if (!actual.product().equals(product)) {
failWithMessage("Expected order product to be <%s> but was <%s>",
product, actual.product());
}
return this;
}
public OrderAssert isPending() {
isNotNull();
if (actual.status() != OrderStatus.PENDING) {
failWithMessage("Expected order to be PENDING but was <%s>", actual.status());
}
return this;
}
public OrderAssert hasQuantityGreaterThan(int min) {
isNotNull();
if (actual.quantity() <= min) {
failWithMessage("Expected quantity > %d but was %d", min, actual.quantity());
}
return this;
}
}
// Usage — reads like plain English
Order order = orderService.place("Gadget", 3);
OrderAssert.assertThat(order)
.hasProduct("Gadget")
.isPending()
.hasQuantityGreaterThan(0);