1. Overview
In this lesson, we’ll learn how AssertJ supports fluent assertions in Java.
We’ll explore its type-specific API across several data types and structures before applying it in a repository test.
The relevant module we need to import when starting this lesson is: advanced-assertions-assertj-start.
If we want to reference the fully implemented lesson, we can import: advanced-assertions-assertj-end.
2. Writing Assertions with AssertJ
AssertJ is a Java library for writing assertions with a fluent API. Its type-specific assertion objects let us chain checks that describe expected behavior.
We’ll start by adding the AssertJ Maven dependency to our project:
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.25.3</version>
<scope>test</scope>
</dependency>
After that, we can create the com.baeldung.lju.AssertJTest class and rewrite the tests using AssertJ. The overloaded Assertions.assertThat() factory methods return type-specific assertion objects. Because Hamcrest exposes a static assertThat() method with the same name, we need to import AssertJ’s entry point from the org.assertj.core.api package:
import static org.assertj.core.api.Assertions.assertThat;
Calling assertThat() with a value gives us assertion methods for that value’s type. IntelliJ code completion can show the available methods for the current type. Let’s inspect the assertions for a String:
Let’s use containsIgnoringCase(), startsWith(), and endsWith() in a string assertion:
String aString = "This is a string";
assertThat(aString)
.containsIgnoringCase("STRING")
.startsWith("This is")
.endsWith("a string");
AssertJ overloads assertThat() for different data types. For a LocalDateTime value, it provides temporal assertions such as isBefore() and isAfter():
LocalDateTime aDate = LocalDateTime.now();
assertThat(aDate)
.isBefore(LocalDateTime.now().plusDays(1))
.isAfter(LocalDateTime.now().minusDays(1));
Additionally, AssertJ provides fluent assertions for Map and Collection objects. For example, we can validate the elements of a Collection using containsExactly() or containsExactlyInAnyOrder():
List<Integer> aList = List.of(4, 3, 2, 1);
assertThat(aList)
.containsExactly(4, 3, 2, 1)
.containsExactlyInAnyOrder(1, 2, 3, 4);
We can also check a value at a specific index or apply a condition to every element. The atIndex() helper requires its own static import:
import static org.assertj.core.data.Index.atIndex;
We can then use it in the assertion chain:
List<Integer> aList = List.of(4, 3, 2, 1);
assertThat(aList)
.allMatch(nr -> nr < 10)
.contains(1, atIndex(3));
For a Map, we can check specific key-value pairs:
Map<String, Integer> aMap = Map.of(
"foo", 2,
"bar", 4,
"qux", 6,
"buzz", 8
);
assertThat(aMap)
.containsEntry("foo", 2)
.containsEntry("bar", 4);
Even if we don’t know all the entries in the Map, we can still validate the presence or absence of specific keys and values:
assertThat(aMap)
.containsKeys("qux", "buzz")
.doesNotContainKey("fizz")
.containsValues(2, 4, 6, 8)
.doesNotContainValue(0);
3. Advanced Assertions in Action
Now that we’re familiar with AssertJ’s API, let’s write a more realistic test for the InMemoryTaskRepository class. We’ll use the findByNameContainingAndAssigneeId() method to query tasks by name and assigneeId.
First, we’ll create the com.baeldung.lju.persistence.repository.impl.InMemoryTaskRepositoryTest class and set up test data by creating a Worker and assigning it three Task instances. One task uses a different naming pattern:
class InMemoryTaskRepositoryTest {
@Test
void givenSomeTasks_whenFindByNameContainingAndAssigneeId_thenReturnFilteredTasks() {
Worker assignee = new Worker("[email protected]", "John", "Doe");
assignee.setId(100L);
var task1 = new Task(
"BACKEND-1001", "description 1", now(), new Campaign(),
TaskStatus.TO_DO, assignee);
var task2 = new Task(
"BACKEND-1002", "description 2", now(), new Campaign(),
TaskStatus.TO_DO, assignee);
var task3 = new Task(
"FRONTEND-1003", "description 3", now(), new Campaign(),
TaskStatus.TO_DO, assignee);
TaskRepository repo = new InMemoryTaskRepository();
repo.save(task1);
repo.save(task2);
repo.save(task3);
// ...
}
}
In this test, we can now request all the “BACKEND” tasks assigned to our test Worker:
List<Task> tasks = repo.findByNameContainingAndAssigneeId("BACKEND", 100L);
Now, we can perform the assertions. We can check that:
- The repository fetches exactly two tasks
- Both tasks are assigned to the worker with an id equal to 100
- None of the tasks contains “FRONTEND” in its name
- The returned tasks equal task1 and task2, regardless of their order
assertThat(tasks)
.hasSize(2)
.allMatch(task -> task.getAssignee().getId() == 100L)
.noneMatch(task -> task.getName().contains("FRONTEND"))
.containsExactlyInAnyOrder(task1, task2);
4. Conclusion
AssertJ’s type-specific, fluent assertions let us combine precise expectations in readable chains. This makes both focused value checks and realistic collection tests easier to follow.