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:

AssertJ code completion

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.