Java Assertion Libs - AssertJ

Writing test cases is a crucial aspect of the software development process for a Java developer. In this article, you will explore AssertJ, an effective library designed to improve the readability and maintainability of your test cases. You will see its benefits, dive into simple and complex assertions, learn how to read assertion errors, and conclude with an overview of its importance in your testing toolkit.

AssertJ is an open-source Java library providing a fluent API for writing assertions in test cases. AssertJ is a popular choice among Java developers thanks to its effective features and compatibility with various testing frameworks like JUnit and TestNG.

Using AssertJ in your Java projects offers several advantages that can enhance your testing experience and improve the overall quality of your software. Here are some key reasons to use AssertJ:

  1. Improved readability: AssertJ's fluent API allows you to write assertions that read like natural language sentences, making your test cases easier to understand and maintain. This increased readability benefits the author of the test and other team members who need to review or modify the tests in the future.
  2. Expressiveness: AssertJ provides a rich set of built-in assertions that cater to various testing scenarios. This expressiveness enables you to write more comprehensive and accurate tests, reducing the chances of missing crucial test cases.
  3. Customizability: AssertJ is highly extensible, allowing you to create custom assertions tailored to your application's specific needs. By leveraging this feature, you can write more relevant and meaningful assertions to your domain, further enhancing test quality and maintainability.
  4. Detailed error messages: When tests fail, AssertJ generates informative and detailed error messages, making it easier to pinpoint the exact cause of the failure. This saves time and effort in debugging and fixing test failures, ultimately leading to a more efficient development process.

Basic assertions

To write asserts using AssertJ, you'll need to add the AssertJ library as a dependency in your project. For example, using either Gradle or Maven, you can add the following to your pom.xml file:

Gradle

testImplementation group: 'org.assertj', name: 'assertj-core', version: '3.21.0'

Maven

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

For Spring Boot projects, it is worth noting that AssertJ is already included as part of the spring-boot-starter-test dependency. This means you can start using AssertJ immediately in your Spring Boot applications without adding additional dependencies.

assertThat is the entry point for assertions in AssertJ. It is a static method you can import from org.assertj.core.api.Assertions and use to create assertions for different types of objects. The method returns an instance of the corresponding Assert class for the given object, which provides a fluent API with a wide range of built-in assertion methods. In the following examples, we will use @Test annotated methods from the JUnit library.

In AssertJ, there are various keywords and methods that you can use to create assertions. These methods help you write expressive and readable tests. Below are some commonly used keywords with examples:

  • isNull() and isNotNull() — These method assert whether or not the actual value is null.
@Test
public void isNullExample() {
    String actual = null;
    String notNullActual = "AssertJ";

    assertThat(actual).isNull();
    assertThat(notNullActual).isNotNull();
}
  • String assertions — These methods in AssertJ provide a set of specialized methods designed to make it easy to perform assertions on string values. Here's an example demonstrating various string assertions:
import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;

public class StringAssertsExample {

    @Test
    public void stringAsserts() {
        String actual = "AssertJ is a great library for writing tests";

        // Check if the string is equal to the expected value (case-insensitive)
        assertThat(actual)
                .isEqualToIgnoringCase("assertj is a great library for writing tests");

        // Check if the string starts with the specified prefix
        assertThat(actual)
                .startsWith("AssertJ");

        // Check if the string ends with the specified suffix
        assertThat(actual)
                .endsWith("writing tests");

        // Check if the string contains the specified substring
        assertThat(actual)
                .contains("great");

        // Check if the string matches the specified regular expression
        assertThat(actual)
                .matches("AssertJ.*library.*writing tests");

        // Check if the string has the specified length
        assertThat(actual)
                .hasSize(42);

        // Check if the string is empty
        String emptyString = "";
        assertThat(emptyString)
                .isEmpty();

        // Check if the string is not empty
        assertThat(actual)
                .isNotEmpty();
    }
}
  • isEqualTo() and isNotEqualTo() — These methods assert whether or not the actual value is equal to the expected value.
@Test
public void isEqualToExample() {
    String actual = "AssertJ";
    String expected = "AssertJ";
    String unexpected = "Assert";

    assertThat(actual).isEqualTo(expected);
    assertThat(actual).isNotEqualTo(unexpected);
}

When using isEqualTo with objects in AssertJ, the method checks for equality based on the object's equals() method. For custom objects, it is essential to properly override the equals() method to ensure that the isEqualTo assertion works as expected.

Here's an example demonstrating the usage of isEqualTo with custom objects:

  1. Custom Person class with a properly implemented equals() method:
public class Person {
    private String firstName;
    private String lastName;
    private int age;

    public Person(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }
    
    public String getFullName() {
            return firstName + " " + lastName;
    }

    // Getters and setters

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        Person person = (Person) obj;
        return age == person.age &&
                Objects.equals(firstName, person.firstName) &&
                Objects.equals(lastName, person.lastName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(firstName, lastName, age);
    }
}

2. Test case using isEqualTo with custom Person objects:

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

@Test
public void isEqualToObjectExample() {
    Person actual = new Person("John", "Doe", 30);
    Person expected = new Person("John", "Doe", 30);

    assertThat(actual).isEqualTo(expected);
}

In this example, the isEqualTo method compares two Person objects, and the assertion passes because the equals() method is correctly implemented. It's important to note that when implementing the equals() method, you should also override the hashCode() method to maintain the contract between equals() and hashCode().

Collection assertions

AssertJ has specific assertions for working with collections such as listssets, and maps. These assertions help you create expressive and readable test cases when working with various types of collections. Here are some examples demonstrating these assertions:

  • Assertions for lists:
@Test
    public void listAsserts() {
        List<String> languages = Arrays.asList("Java", "Python", "JavaScript");

        // Check if the list contains the given elements (order doesn't matter)
        assertThat(languages)
                .contains("Python", "Java");

        // Check if the list contains exactly the given elements (and nothing else) in any order
        assertThat(languages)
                .containsExactlyInAnyOrder("JavaScript", "Python", "Java");

        // Check if the list contains the given elements in the specified order
        assertThat(languages)
                .containsExactly("Java", "Python", "JavaScript");

        // Check if the list doesn't contain the given element
        assertThat(languages)
                .doesNotContain("PHP");
    }
  • Assertions for sets:
@Test
    public void setAsserts() {
        Set<String> languages = new HashSet<>(Arrays.asList("Java", "Python", "JavaScript"));

        // Check if the set contains the given elements
        assertThat(languages)
                .contains("Python", "Java");

        // Check if the set contains exactly the given elements (and nothing else)
        assertThat(languages)
                .containsExactlyInAnyOrder("JavaScript", "Python", "Java");

        // Check if the set doesn't contain the given element
        assertThat(languages)
                .doesNotContain("PHP");
    }
  • Assertions for maps:
@Test
    public void mapAsserts() {
        Map<String, Integer> languageRanks = new HashMap<>();
        languageRanks.put("Java", 1);
        languageRanks.put("Python", 2);
        languageRanks.put("JavaScript", 3);

        // Check if the map contains the given entry
        assertThat(languageRanks)
                .containsEntry("Python", 2);

        // Check if the map contains the given entries
        assertThat(languageRanks)
                .containsEntries(
                        new HashMap.SimpleEntry<>("Java", 1),
                        new HashMap.SimpleEntry<>("Python", 2)
                );

        // Check if the map contains the given key
        assertThat(languageRanks)
                .containsKey("Java");

        // Check if the map contains the given value
        assertThat(languageRanks)
                .containsValue(3);

        // Check if the map has the expected size
        assertThat(languageRanks)
                .hasSize(3);

        // Check if the map doesn't contain the given key
        assertThat(languageRanks)
                .doesNotContainKey("PHP");
    }

These examples demonstrate various collection assertions provided by AssertJ, such as checking for the presence of specific elements, keys, values, and the size of the collections. Using these assertions, you can create comprehensive test cases for your code when working with collections like lists, sets, and maps.

Object assertions

AssertJ provides assertions for verifying properties of classes and objects, such as the object type, the values of their fields, and whether they are instances of certain classes or interfaces. Here are some examples demonstrating class and object assertions:

  • Type and instance assertions
 @Test
    public void typeAsserts() {
        Object number = 42;

        // Check if the object is an instance of the specified class
        assertThat(number)
                .isInstanceOf(Integer.class);

        // Check if the object is an instance of any of the specified classes
        assertThat(number)
                .isInstanceOfAny(Integer.class, Long.class);

        // Check if the object is not an instance of the specified class
        assertThat(number)
                .isNotInstanceOf(String.class);

        // Check if the object is not an instance of any of the specified classes
        assertThat(number)
                .isNotInstanceOfAny(String.class, Float.class);
    }
  • Field and property assertions — For the following examples, we will use the previously declared Person class:
@Test
    public void fieldAsserts() {
        Person person = new Person("John", "Doe", 30);

        // Check if the object has the specified field with the given value
        assertThat(person)
                .hasFieldOrPropertyWithValue("firstName", "John");

        // Check if the object has the specified field (or property)
        assertThat(person)
                .hasFieldOrProperty("lastName");

        // Check if the object has the specified fields (or properties)
        assertThat(person)
                .hasFieldOrProperties("firstName", "age");

        // Check if the object doesn't have the specified field (or property)
        assertThat(person)
                .doesNotHaveFieldOrProperty("middleName");

        // Check if the object doesn't have the specified fields (or properties)
        assertThat(person)
                .doesNotHaveFieldOrProperties("middleName", "nickName");

        // Check if the object has a property with the given value
        assertThat(person)
                .hasPropertyWithValue("fullName", "John Doe");
    }

Often, when you work with object asserts, you need to check several properties of an object at once. The satisfies assertion in AssertJ allows you to perform custom assertions or a series of assertions on an object using a lambda expression, a method reference, or an anonymous inner class. It accepts a Consumer functional interface, which represents a function that takes a single argument and returns no result.

@Test
    public void satisfiesAsserts() {
        Person person = new Person("John", 30);

        // Check if the object satisfies the given condition(s)
        assertThat(person)
                .satisfies(p -> {
                    assertThat(p.firstName).isEqualTo("John");
                    assertThat(p.age).isGreaterThanOrEqualTo(18);
                });
    }

AssertJ also provides assertions for working with the Optional class introduced in Java 8. These assertions help you create expressive and readable test cases when dealing with Optional objects. Here are some examples demonstrating assertions with the Optional class:

@Test
    public void optionalAsserts() {
        Optional<String> presentOptional = Optional.of("AssertJ");
        Optional<String> emptyOptional = Optional.empty();

        // Check if the Optional is present (i.e., contains a value)
        assertThat(presentOptional)
                .isPresent();

        // Check if the Optional is empty (i.e., doesn't contain a value)
        assertThat(emptyOptional)
                .isEmpty();

        // Check if the Optional contains the specified value
        assertThat(presentOptional)
                .contains("AssertJ");

        // Check if the Optional doesn't contain the specified value
        assertThat(presentOptional)
                .doesNotContain("JUnit");

        // Check if the Optional contains a value that matches the given condition
        assertThat(presentOptional)
                .hasValueSatisfying(value -> assertThat(value).startsWith("Assert"));

        // Perform assertions on the value inside the Optional (if present)
        assertThat(presentOptional)
                .hasValueSatisfying(value -> {
                    assertThat(value).isNotEmpty();
                    assertThat(value).isEqualTo("AssertJ");
                });
    }

Using these assertions, you can create comprehensive test cases to ensure that your objects have the expected state and behavior.

Throwable assertions

AssertJ provides a way to assert exceptions that are thrown during the execution of code. It enables you to verify if an exception of a specific type is thrown and if its message or cause matches your expectations. Here's an example demonstrating how to assert exceptions using AssertJ:

 @Test
    public void exceptionAsserts() {
        // Assert that an exception of the specified type is thrown
        assertThatThrownBy(() -> {
            throw new IllegalArgumentException("Invalid argument");
        }).isInstanceOf(IllegalArgumentException.class);

        // Assert that an exception with the specified message is thrown
        assertThatThrownBy(() -> {
            throw new IllegalArgumentException("Invalid argument");
        }).isInstanceOf(IllegalArgumentException.class)
          .hasMessage("Invalid argument");

        // Assert that an exception with a message matching the specified pattern is thrown
        assertThatThrownBy(() -> {
            throw new IllegalArgumentException("Invalid argument");
        }).isInstanceOf(IllegalArgumentException.class)
          .hasMessageMatching("Invalid .*");

        // Assert that an exception with the specified cause is thrown
        Exception cause = new NullPointerException("Cause");
        assertThatThrownBy(() -> {
            throw new RuntimeException("Wrapper exception", cause);
        }).isInstanceOf(RuntimeException.class)
          .hasCause(cause);

        // Assert that an exception with a cause of the specified type is thrown
        assertThatThrownBy(() -> {
            throw new RuntimeException("Wrapper exception", cause);
        }).isInstanceOf(RuntimeException.class)
          .hasCauseInstanceOf(NullPointerException.class);
    }

In this example, we use the assertThatThrownBy method to assert that an exception of a specific type is thrown when executing a lambda expression. We then chain additional assertions to verify the exception's message or cause.

Using these exception assertions, you can create comprehensive test cases to ensure that your code throws the expected exceptions with the correct messages and causes.

How to read assert errors

Reading and understanding assert errors in AssertJ is crucial for diagnosing and fixing issues in your code. AssertJ provides descriptive error messages that give you insights into what went wrong during your test execution.

When an AssertJ assertion fails, it throws an AssertionError with a detailed error message. The error message typically includes the following information:

  1. The expected condition — The condition you expected to be true, based on your assertion.
  2. The actual value — The value that was produced during the test execution.
  3. A description of the failure — A human-readable description explaining the difference between the expected and actual values.

Here's an example of a failing assertion and its error message:

@Test
    public void failingAssert() {
        String actual = "hello";
        String expected = "world";

        assertThat(actual).isEqualTo(expected);
    }

The above assertion will fail, and the error message will look like this:

org.opentest4j.AssertionFailedError: 
Expecting:
 <"hello">
to be equal to:
 <"world">
but was not.

From this error message, you can easily understand that the actual value hello was not equal to the expected value world.

To make the error messages even more descriptive, you can use the as or describedAs methods to add a custom description to your assertions:

@Test
public void failingAssertWithDescription() {
    String actual = "hello";
    String expected = "world";

    assertThat(actual)
            .as("Checking if actual and expected strings are equal")
            .isEqualTo(expected);
}

The error message for the above assertion will now include the custom description:

org.opentest4j.AssertionFailedError: [Checking if actual and expected strings are equal] 
Expecting:
 <"hello">
to be equal to:
 <"world">
but was not.

By carefully reading the error messages produced by AssertJ assertions, you can better understand the discrepancies between expected and actual values, which will help you identify and fix issues in your code more efficiently.

Conclusion

In conclusion, AssertJ is a powerful and expressive Java library for writing assertions in your test cases. It offers a wide range of assertions for various data types, such as primitives, strings, collections, and custom objects and assertions for exceptions. The library's fluent API and descriptive error messages make your tests more readable and maintainable, allowing you to understand the intent and diagnose any issues easily.

By incorporating AssertJ into your testing toolkit, you can improve the quality of your test cases and boost your confidence with regards the correctness of your code. With its comprehensive set of features, AssertJ empowers developers to write more expressive, reliable, and maintainable tests, ultimately leading to higher-quality software.

Create a free account to access the full topic

“It has all the necessary theory, lots of practice, and projects of different levels. I haven't skipped any of the 3000+ coding exercises.”
Andrei Maftei
Hyperskill Graduate