Writing Assertions With JUnit 5 and AssertJ

If you want to learn how you can work smarter and save time when you are writing tests with JUnit 5, you should take a look at my Introduction to JUnit 5 course. It has 24 lessons, 47 exercises, and 13 quizzes.

This blog post describes how we can write assertions with AssertJ when we are writing tests with JUnit 5. After we have finished this blog post, we:

  • Can get the required dependencies with Maven and Gradle.
  • Know how we can write basic assertions with AssertJ.
  • Understand how we can customize the error message shown when an assertion fails.
  • Can write soft assertions with AssertJ.

Let's begin.

Getting the Required Dependencies

Before we can write assertions with AssertJ, we have to ensure that the assertj-core dependency (version 3.21.0) is found from the classpath.

If we are using Maven, we have to add the assertj-core dependency to the test scope. We can do this by adding the following snippet to the dependencies section of our pom.xml file:

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

If we are using Gradle, we have to add the assertj-core dependency to the testImplementation dependency configuration. We can do this by adding the following snippet to our build.gradle file:

dependencies {
    testImplementation(
            'org.assertj:assertj-core:3.21.0'
    )
}

After we have added this dependency to the classpath, we can write assertions with AssertJ. Let's find out how we can do it.

Writing Assertions With AssertJ

When we want to write assertions with AssertJ, we have to use the static assertThat() method of the org.assertj.core.api.Assertions class. When we invoke this method, we have to know these two things:

  • The assertThat() method takes the actual value or object as a method parameter.
  • The Assertions() class uses method overloading and the type of the object returned by the assertThat() method depends from the type of the argument passed to that method.

After we have invoked the assertThat() method, we can write our assertions by using the returned assertion object. Also, because AssertJ provides a fluent API, each assertion method returns a reference to the used assertion object. This means that we can chain assertions by simply invoking another assertion method.

Next, we will take a look at some examples which demonstrate how we can write assertions with AssertJ.

Asserting Boolean Values

If we want to verify that a boolean value is true, we have to write our assertion by invoking the isTrue() method of the AbstractBooleanAssert class. In other words, we have to use an assertion that looks as follows:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

@DisplayName("Write assertions for booleans")
class BooleanAssertionTest {

    @Nested
    @DisplayName("When boolean is true")
    class WhenBooleanIsTrue {

        @Test
        @DisplayName("Should be true")
        void shouldBeTrue() {
            assertThat(true).isTrue();
        }
    }
}

If we want to verify that a boolean value is false, we have to write our assertion by invoking the isFalse() method of the AbstractBooleanAssert class. In other words, we have to use an assertion that looks as follows:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

@DisplayName("Write assertions for booleans")
class BooleanAssertionTest {

    @Nested
    @DisplayName("When boolean is false")
    class WhenBooleanIsFalse {

        @Test
        @DisplayName("Should be false")
        void shouldBeFalse() {
            assertThat(false).isFalse();
        }
    }
}

Let’s move on and find out how we can verify that an object is null or isn't null.

Asserting That an Object Is Null or Isn't Null

If we want to verify that an object is null, we have to write our assertion by invoking the isNull() method of the AbstractAssert class. In other words, we have to use an assertion that looks as follows:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

@DisplayName("Writing assertions for objects")
class ObjectAssertionTest {

    @Nested
    @DisplayName("When object is null")
    class WhenObjectIsNull {

        private final Object NULL = null;

        @Test
        @DisplayName("Should be null")
        void shouldBeNull() {
            assertThat(NULL).isNull();
        }
    }
}

If we want to verify that an object isn't null, we have to write our assertion by invoking the isNotNull() method of the AbstractAssert class. In other words, we have to use an assertion that looks as follows:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

@DisplayName("Writing assertions for objects")
class ObjectAssertionTest {

    @Nested
    @DisplayName("When object is not null")
    class WhenObjectIsNotNotNull {

        @Test
        @DisplayName("Should not be null")
        void shouldNotBeNull() {
            assertThat(new Object()).isNotNull();
        }
    }
}

Next, we will find out how we can verify that two objects (or values) are equal or aren't equal.

Asserting That Two Objects or Values Are Equal

If we want to verify that the expected value (or object) is equal to the actual value (or object), we have to write our assertion by invoking either the isEqualTo() method of the AbstractAssert class or the isEqualByComparingTo() method of the AbstractComparableAssert class. The difference of these methods is explained in the following:

  • The isEqualTo() method invokes the equals() method.
  • The isEqualByComparingTo() method invokes the compareTo() method of the Comparable interface.

For example, if we want to ensure that two integers are equal, we have to use an assertion that looks as follows:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

@DisplayName("Writing assertions for objects")
class ObjectAssertionTest {

    @Nested
    @DisplayName("When two objects are equal")
    class WhenTwoObjectsAreEqual {

        @Nested
        @DisplayName("When objects are integers")
        class WhenObjectsAreIntegers {

            private final Integer ACTUAL = 9;
            private final Integer EXPECTED = 9;

            @Test
            @DisplayName("Should be equal")
            void shouldBeEqual() {
                assertThat(ACTUAL).isEqualByComparingTo(EXPECTED);
            }
        }
    }
}

If we want to verify that the expected value (or object) isn't equal to the actual value (or object), we have to write our assertion by invoking either the isNotEqualTo() method of the AbstractAssert class or the isNotEqualByComparingTo() method of the AbstractComparableAssert class. These methods are implemented in the same way as the isEqualTo() and isEqualByComparingTo() methods.

For example, if we want to ensure that two integers are not equal, we have to use an assertion that looks as follows:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

@DisplayName("Writing assertions for objects")
class ObjectAssertionTest {

    @Nested
    @DisplayName("When two objects aren't equal")
    class WhenTwoObjectsAreNotEqual {

        @Nested
        @DisplayName("When objects are integers")
        class WhenObjectsAreIntegers {

            private final Integer ACTUAL = 9;
            private final Integer EXPECTED = 4;

            @Test
            @DisplayName("Should not be equal")
            void shouldNotBeEqual() {
                assertThat(ACTUAL).isNotEqualByComparingTo(EXPECTED);
            }
        }
    }
}

Let’s move on and find out how we can write assertions for object references.

Asserting Object References

If we want to ensure that two objects refer to the same object, we have to write our assertion by invoking the isSameAs() method of the AbstractAssert class. In other words, we have to use an assertion that looks as follows:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

@DisplayName("Writing assertions for objects")
class ObjectAssertionTest {

    @Nested
    @DisplayName("When two objects refer to the same object")
    class WhenTwoObjectsReferToSameObject {

        private final Object ACTUAL = new Object();
        private final Object EXPECTED = ACTUAL;

        @Test
        @DisplayName("Should refer to the same object")
        void shouldReferToSameObject() {
            assertThat(ACTUAL).isSameAs(EXPECTED);
        }
    }
}

If we want to ensure that two objects don’t refer to the same object, we have to write our assertion by invoking the isNotSameAs() method of the AbstractAssert class. In other words, we have to use an assertion that looks as follows:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

@DisplayName("Writing assertions for objects")
class ObjectAssertionTest {

    @Nested
    @DisplayName("When two objects don't refer to the same object")
    class WhenTwoObjectsDoNotReferToSameObject {

        private final Object ACTUAL = new Object();
        private final Object EXPECTED = new Object();

        @Test
        @DisplayName("Should not refer to the same object")
        void shouldNotReferToSameObject() {
            assertThat(ACTUAL).isNotSameAs(EXPECTED);
        }
    }
}

Next, we will find out how we can verify that two arrays are equal or aren't equal.

Asserting That Two Arrays Are Equal

If we want to verify that two arrays are equal, we have to write our assertion by invoking the isEqualTo() method of the AbstractArrayAssert class. In other words, we have to use an assertion that looks as follows:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

@DisplayName("Write assertions for arrays")
class ArrayAssertionTest {

    @Nested
    @DisplayName("When two arrays are equal")
    class WhenArraysAreEqual {

        @Nested
        @DisplayName("When arrays contain integers")
        class WhenArraysContainIntegers {

            final int[] ACTUAL = new int[]{2, 5, 7};
            final int[] EXPECTED = new int[]{2, 5, 7};

            @Test
            @DisplayName("Should contain the same integers")
            void shouldContainSameIntegers() {
                assertThat(ACTUAL).isEqualTo(EXPECTED);
            }
        }
    }
}
Two arrays are considered as equal if:

  • They are both null or empty.
  • Both arrays contain the "same" objects or values. To be more specific, JUnit 5 iterates both arrays one element at a time and ensures that the elements found from the given index are equal.

If we want to verify that two arrays aren't equal, we have to write our assertion by invoking the isNotEqualTo() method of the AbstractArrayAssert class. In other words, we have to use an assertion that looks as follows:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

@DisplayName("Write assertions for arrays")
class ArrayAssertionTest {

    @Nested
    @DisplayName("When two arrays are not equal")
    class WhenArraysAreNotEqual {

        @Nested
        @DisplayName("When arrays contain integers")
        class WhenArraysContainIntegers {

            final int[] ACTUAL = new int[]{2, 6, 7};
            final int[] EXPECTED = new int[]{2, 5, 7};

            @Test
            @DisplayName("Should not contain the same integers")
            void shouldNotContainSameIntegers() {
                assertThat(ACTUAL).isNotEqualTo(EXPECTED);
            }
        }
    }
}
By the way, we can write quite sophisticated assertions for arrays with AssertJ. Because I don't want to repeat myself and these assertions use a similar API than assertions written for an Iterable, I will describe these assertions in the next section of this blog post.

Additional Reading:

Let's move on and find out how we can write assertions for iterables.

Writing Assertions for Iterables

If we want to write an assertion which verifies that the size of an Iterable is correct, we can use one of these three options:

  • If we want to verify that an iterable is empty, we can write our assertion by invoking the isEmpty() method of the AbstractIterableAssert class.
  • If we want to ensure that an iterable isn't empty, we can write our assertion by invoking the isNotEmpty() method of the AbstractIterableAssert class.
  • If we want to verify that the size of the iterable is correct, we can write our assertion by invoking the hasSize() method of the AbstractIterableAssert class.

For example, if we want to ensure that a list contains two elements, we have to use an assertion that looks as follows:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.List;

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

@DisplayName("Writing assertions for lists")
class ListAssertionTest {

    @Nested
    @DisplayName("When we write assertions for elements")
    class WhenWeWriteAssertionsForElements {

        private Object first;
        private Object second;

        private List<Object> list;

        @BeforeEach
        void createAndInitializeList() {
            first = new Object();
            second = new Object();

            list = Arrays.asList(first, second);
        }

        @Test
        @DisplayName("Should contain two elements")
        void shouldContainTwoElements() {
            assertThat(list).hasSize(2);
        }
    }
}

If we want to ensure that an iterable contains only the expected elements in the given order, we have to write our assertion by using the containsExactly() method of the AbstractIterableAssert class. For example, if we want to verify that our list contains the correct elements in the given order, we have to use this assertion:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.List;

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

@DisplayName("Writing assertions for lists")
class ListAssertionTest {

    @Nested
    @DisplayName("When we write assertions for elements")
    class WhenWeWriteAssertionsForElements {

        private Object first;
        private Object second;

        private List<Object> list;

        @BeforeEach
        void createAndInitializeList() {
            first = new Object();
            second = new Object();

            list = Arrays.asList(first, second);
        }

        @Test
        @DisplayName("Should contain the correct elements in the given order")
        void shouldContainCorrectElementsInGivenOrder() {
            assertThat(list).containsExactly(first, second);
        }
    }
}

If we want to verify that an iterable contains only the expected elements in any order, we have to to write our assertion by using the containsExactlyInAnyOrder() method of the AbstractIterableAssert class. For example, if we want to verify that our list contains the correct elements in any order, we have to use this assertion:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.List;

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

@DisplayName("Writing assertions for lists")
class ListAssertionTest {

    @Nested
    @DisplayName("When we write assertions for elements")
    class WhenWeWriteAssertionsForElements {

        private Object first;
        private Object second;

        private List<Object> list;

        @BeforeEach
        void createAndInitializeList() {
            first = new Object();
            second = new Object();

            list = Arrays.asList(first, second);
        }
        
        @Test
        @DisplayName("Should contain the correct elements in any order")
        void shouldContainCorrectElementsInAnyOrder() {
            assertThat(list).containsExactlyInAnyOrder(second, first);
        }
    }
}

If we want to ensure that an iterable contains the specified element, we have to to write our assertion by using the containsOnlyOnce() method of the AbstractIterableAssert class. For example, if we want to verify that our list contains the Object that's stored in the field called first, we have to use this assertion:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.List;

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

@DisplayName("Writing assertions for lists")
class ListAssertionTest {

    @Nested
    @DisplayName("When we write assertions for elements")
    class WhenWeWriteAssertionsForElements {

        private Object first;
        private Object second;

        private List<Object> list;

        @BeforeEach
        void createAndInitializeList() {
            first = new Object();
            second = new Object();

            list = Arrays.asList(first, second);
        }

        @Test
        @DisplayName("Should contain the correct element once")
        void shouldContainCorrectElementOnce() {
            assertThat(list).containsOnlyOnce(first);
        }
    }
}

If we want to ensure that an iterable doesn't contain the specified element, we have to to write our assertion by using the doesNotContain() method of the AbstractIterableAssert class. For example, if we want to verify that our list doesn't contain the specified object, we have to use this assertion:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.List;

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

@DisplayName("Writing assertions for lists")
class ListAssertionTest {

    @Nested
    @DisplayName("When we write assertions for elements")
    class WhenWeWriteAssertionsForElements {

        private Object first;
        private Object second;

        private List<Object> list;

        @BeforeEach
        void createAndInitializeList() {
            first = new Object();
            second = new Object();

            list = Arrays.asList(first, second);
        }

        @Test
        @DisplayName("Should not contain an incorrect element")
        void shouldNotContainIncorrectElement() {
            assertThat(list).doesNotContain(new Object());
        }
    }
}

If we want to verify that two iterables are deeply equal, we have to to write our assertion by using the isEqualTo() method of the AbstractAssert class. For example, if we want verify that two Integer lists are deeply equal, we have to use this assertion:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.List;

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

@DisplayName("Writing assertions for lists")
class ListAssertionTest {

    @Nested
    @DisplayName("When we compare two lists")
    class WhenWeCompareTwoLists {

        private final List<Integer> FIRST = Arrays.asList(1, 2, 3);
        private final List<Integer> SECOND = Arrays.asList(1, 2, 3);

        @Test
        @DisplayName("Should contain the same elements")
        void shouldContainSameElements() {
            assertThat(FIRST).isEqualTo(SECOND);
        }
    }
}
Two iterables are considered as equal if:

  • They are both null or empty.
  • Both iterables contain the "same" objects or values. To be more specific, JUnit 5 iterates both iterables one element at a time and ensures that the elements found from the given index are equal.

Additional Reading:

Next, we will find out how we can write assertions for maps.

Writing Assertions for Maps

If we want to ensure that a map contains the specified key, we have to write our assertion by invoking the containsKey() method of the AbstractMapAssert class. In other words, we have to use an assertion that looks as follows:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.Map;

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


@DisplayName("Writing assertions for maps")
class MapAssertionTest {

    private static final String INCORRECT_KEY = "incorrectKey";
    private static final String KEY = "key";
    private static final String VALUE = "value";

    private Map<String, String> map;

    @BeforeEach
    void createAndInitializeMap() {
        map = new HashMap<>();
        map.put(KEY, VALUE);
    }

    @Nested
    @DisplayName("When we verify that the map contains the given key")
    class WhenWeVerifyThatMapContainsGivenKey {

        @Test
        @DisplayName("Should contain the correct key")
        void shouldContainCorrectKey() {
            assertThat(map).containsKey(KEY);
        }
    }
}

If we want to ensure that a map doesn't contain the specified key, we have to write our assertion by invoking the doesNotContainKey() method of the AbstractMapAssert class. In other words, we have to use an assertion that looks as follows:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.Map;

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


@DisplayName("Writing assertions for maps")
class MapAssertionTest {

    private static final String INCORRECT_KEY = "incorrectKey";
    private static final String KEY = "key";
    private static final String VALUE = "value";

    private Map<String, String> map;

    @BeforeEach
    void createAndInitializeMap() {
        map = new HashMap<>();
        map.put(KEY, VALUE);
    }

    @Nested
    @DisplayName("When we verify that the map doesn't contain the given key")
    class WhenWeVerifyThatMapDoesNotContainGivenKey {

        @Test
        @DisplayName("Should not contain the incorrect key")
        void shouldNotContainIncorrectKey() {
            assertThat(map).doesNotContainKey(INCORRECT_KEY);
        }
    }
}

If we want to ensure that a map contains the specified entry, we have to write our assertion by invoking the containsEntry() method of the AbstractMapAssert class. In other words, we have to use an assertion that looks as follows:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.Map;

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


@DisplayName("Writing assertions for maps")
class MapAssertionTest {

    private static final String INCORRECT_KEY = "incorrectKey";
    private static final String KEY = "key";
    private static final String VALUE = "value";

    private Map<String, String> map;

    @BeforeEach
    void createAndInitializeMap() {
        map = new HashMap<>();
        map.put(KEY, VALUE);
    }

    @Nested
    @DisplayName("When we verify that the map contains the given entry")
    class WhenWeVerifyThatMapContainsGivenEntry {

        @Test
        @DisplayName("Should contain the given entry")
        void shouldContainGivenEntry() {
            assertThat(map).containsEntry(KEY, VALUE);
        }
    }
}

If we want to ensure that a map doesn't contain the specified entry, we have to write our assertion by invoking the doesNotContainEntry() method of the AbstractMapAssert class. In other words, we have to use an assertion that looks as follows:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.Map;

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


@DisplayName("Writing assertions for maps")
class MapAssertionTest {

    private static final String INCORRECT_KEY = "incorrectKey";
    private static final String KEY = "key";
    private static final String VALUE = "value";

    private Map<String, String> map;

    @BeforeEach
    void createAndInitializeMap() {
        map = new HashMap<>();
        map.put(KEY, VALUE);
    }

    @Nested
    @DisplayName("When we verify that the map doesn't contain the given entry")
    class WhenWeVerifyThatMapDoesNotContainGivenEntry {

        @Test
        @DisplayName("Should not contain the given entry")
        void shouldContainGivenEntry() {
            assertThat(map).doesNotContainEntry(INCORRECT_KEY, VALUE);
        }
    }
}

Let's move on and find out how we can write assertions for the exceptions thrown by the system under test.

Writing Assertions for Exceptions

If we want to write assertions for the exception thrown by the system under test, we can use one of these two options:

First, we can use the the static assertThatThrownBy() method of the org.assertj.core.api.Assertions class. When we use this method, we have to know these two things:

  • It takes a ThrowingCallable object as a method parameter. This object invokes the system under test.
  • It returns an AbstractThrowableAssert object. We have to use this object when we write assertions for the exception thrown by the system under test.

Let's take a look at two examples which demonstrate how we can write assertions by using this approach:

If we want to verify that the system under test throws the expected exception, we have to write our assertion by using the isExactlyInstanceOf() method of the AbstractThrowableAssert class. For example, if we want to verify that the system under test throws a NullPointerException, we have to write an assertion that looks as follows:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

@DisplayName("Writing assertions for exceptions")
class ExceptionAssertionTest {

    @Nested
    @DisplayName("When we write assertions directly to the thrown exception")
    class WhenWeWriteAssertionsForThrownException {

        @Nested
        @DisplayName("When the system under test throws the correct exception")
        class WhenSystemUnderTestThrowsException {

            @Test
            @DisplayName("Should throw the correct exception")
            void shouldThrowCorrectException() {
                assertThatThrownBy(() -> { throw new NullPointerException(); })
                        .isExactlyInstanceOf(NullPointerException.class);
            }
        }
    }
}

If we want to verify that the system under test throws an exception that has the expected message, we have to write our assertion by using the hasMessage() method of the AbstractThrowableAssert class. For example, if we want to verify that the system under test throws an exception that has the message: 'Hello World!', we have to write an assertion that looks as follows:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

@DisplayName("Writing assertions for exceptions")
class ExceptionAssertionTest {

    @Nested
    @DisplayName("When we write assertions directly to the thrown exception")
    class WhenWeWriteAssertionsForThrownException {

        @Nested
        @DisplayName("When SUT throws an exception that has the correct message")
        class WhenSystemUnderTestThrowsExceptionWithCorrectMessage {

            @Test
            @DisplayName("Should throw an exception that has the correct message")
            void shouldThrowAnExceptionWithCorrectMessage() {
                assertThatThrownBy(() -> { 
                    throw new NullPointerException("Hello World!"); 
                })
                        .hasMessage("Hello World!");
            }
        }
    }
}

Second, we can catch the thrown exception by using the static catchThrowable() method of the org.assertj.core.api.Assertions class. This method can take two method parameters which are described in the following:

  • A ThrowingCallable object that invokes the system under test.
  • A Class object that specifies the type of the expected exception. This is an optional parameter, and if we pass it to the catchThrowable() method, it specifies the type of the returned exception object. If we omit this method parameter, the catchThrowable() method returns a Throwable object.

Let's take a look at two examples which demonstrate how we can write assertions by using this approach:

If we want to verify that the system under test throws the expected exception, we have to write our assertion by using the isExactlyInstanceOf() method of the AbstractThrowableAssert class. For example, if we want to verify that the system under test throws a NullPointerException, we have to write an assertion that looks as follows:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

@DisplayName("Writing assertions for exceptions")
class ExceptionAssertionTest {

    @Nested
    @DisplayName("When we catch the thrown exception object")
    class WhenWeCatchThrownExceptionObject {

        @Nested
        @DisplayName("When the system under test throws the correct exception")
        class WhenSystemUnderTestThrowsException {

            @Test
            @DisplayName("Should throw the correct exception")
            void shouldThrowCorrectException() {
                final Throwable thrown = catchThrowable(() -> { 
                    throw new NullPointerException(); 
                });
                assertThat(thrown).isExactlyInstanceOf(NullPointerException.class);
            }
        }
    }
}

If we want to verify that the system under test throws an exception that has the expected message, we have to write our assertion by using the hasMessage() method of the AbstractThrowableAssert class. For example, if we want to verify that the system under test throws an exception that has the message: 'Hello World!', we have to write an assertion that looks as follows:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

@DisplayName("Writing assertions for exceptions")
class ExceptionAssertionTest {

    @Nested
    @DisplayName("When we catch the thrown exception object")
    class WhenWeCatchThrownExceptionObject {

        @Nested
        @DisplayName("When the system under test throws an exception that has the correct message")
        class WhenSystemUnderTestThrowsExceptionWithCorrectMessage {

            @Test
            @DisplayName("Should throw an exception that has the correct message")
            void shouldThrowAnExceptionWithCorrectMessage() {
                final Throwable thrown = catchThrowable(() -> {
                    throw new NullPointerException("Hello World!"); }
                    );
                assertThat(thrown.getMessage()).isEqualTo("Hello World!");
            }
        }
    }
}
If we have to get access to the actual exception object, we must use the catchThrowable() method. Otherwise, we should write our assertion by using the assertThatThrownBy() method.

Additional Reading:

Next, we will find out how we can write assertions for Optional objects.

Writing Assertions for Optional Objects

If we want to ensure that an Optional object is empty, we have to write our assertion by invoking the isEmpty() method of the AbstractOptionalAssert class. In other words, we have to write an assertion that looks as follows:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.Optional;

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

@DisplayName("Writing assertions for Optional objects")
class OptionalAssertionTest {

    @Nested
    @DisplayName("When the optional is empty")
    class WhenOptionalIsEmpty {

        @Test
        @DisplayName("Should be empty")
        void shouldBeEmpty() {
            assertThat(Optional.empty()).isEmpty();
        }
    }
}

If we want to ensure that an Optional object isn't empty, we have to write our assertion by invoking the isNotEmpty() method of the AbstractOptionalAssert class. In other words, we have to write an assertion that looks as follows:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.Optional;

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

@DisplayName("Writing assertions for Optional objects")
class OptionalAssertionTest {

    @Nested
    @DisplayName("When the optional is not empty")
    class WhenOptionalIsNotEmpty {

        @Test
        @DisplayName("Should not be empty")
        void shouldNotBeEmpty() {
            assertThat(Optional.of(new Object())).isNotEmpty();
        }
    }
}

If we want to ensure that an Optional object contains the expected object, we have to write our assertion by invoking the contains() method of the AbstractOptionalAssert class. In other words, we have to write an assertion that looks as follows:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.Optional;

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

@DisplayName("Writing assertions for Optional objects")
class OptionalAssertionTest {

    @Nested
    @DisplayName("When the optional is not empty")
    class WhenOptionalIsNotEmpty {

        private final Object OBJECT = new Object();

        @Test
        @DisplayName("Should contain the correct object")
        void shouldContainCorrectObject() {
            assertThat(Optional.of(OBJECT)).contains(OBJECT);
        }
    }
}

Let's move on and find out how we can specify a custom error message that's shown when an assertion fails.

Providing a Custom Error Message

Even though AssertJ has very good error messages, sometimes we want to emphasize the business rule that's enforced by our assertion. When we want to do this, we can provide a custom error message by using one of these two options:

  • If we want to override the description part of the error message, we have to invoke either the as() or the describedAs() method of the Descriptable interface.
  • If we want to override the entire error message, we have to invoke the overridingErrorMessage() method of the AbstractAssert class.

All these methods can take two method parameters which are explained in the following:

  • A String object which contains the error message. If we want to provide a dynamic error message, we can use the format supported by the String.format() method.
  • An optional Object array which contains the parameters of our error message. This method parameter is passed to the String.format() method that creates the actual error message. That's why we don't have to give this method parameter if we use a static error message.

Let's take a look at two examples which demonstrate the difference of these options.

First, if we want to override only the description part of the shown error message, we have to write an assertion that looks as follows:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

@DisplayName("Provide a custom error message")
class CustomErrorMessageTest {

    @Nested
    @DisplayName("When we provide only the description")
    class WhenWeProvideOnlyDescription {

        @Test
        @DisplayName("Should override only the description")
        void shouldBeFalseWithCustomErrorMessage() {
            assertThat(false)
                    .describedAs("The boolean is not false")
                    .isFalse();
        }
    }
}

If our assertion fails, we see an error message that looks as follows:

org.opentest4j.AssertionFailedError: [The boolean is not false] 
Expecting: <true> to be equal to: <false> but was not.
Expected :false
Actual   :true

Second, if we want to override the entire error message, we have to write an assertion that looks as follows:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

@DisplayName("Provide a custom error message")
class CustomErrorMessageTest {

    @Nested
    @DisplayName("When we provide the entire error message")
    class WhenWeProvideEntireErrorMessage {

        @Test
        @DisplayName("Should override entire error message")
        void shouldBeFalseWithCustomErrorMessage() {
            assertThat(false)
                    .overridingErrorMessage("The boolean is not false")
                    .isFalse();
        }
    }
}

If our assertion fails, we see an error message that looks as follows:

java.lang.AssertionError: The boolean is not false

If I want to provide a custom error message, I use the overridingErrorMessage() method because I think that it allows me write clean error messages. However, if you want to use the describedAs() method, feel free to do so.

Additional Reading:

Let's move on and find out how we can write soft assertions with AssertJ.

Writing Soft Assertions

If we have to write a test that requires multiple assertions, it's a good idea to run all assertions and report all assertion failures after all assertions have been run. This ensures that we have to run our test only once. We can do this with AssertJ by using soft assertions.

Let’s assume that we have to write an assertion which verifies that a Person object has the correct name. The source code of the Person class looks as follows:

public class Person {
 
    private String firstName;
    private String lastName;
 
    public Person() {}
 
    public String getFirstName() {
        return firstName;
    }
 
    public String getLastName() {
        return lastName;
    }
 
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
 
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

As we can see, if we want to verify that a person has the correct name, we have to verify that the asserted Person object has the correct first and last name. We can write our assertion by following these steps:

  1. Create a new SoftAssertions object.
  2. Ensure that the Person object has the correct first name.
  3. Verify that the Person object has the correct last name.
  4. Run all assertions by invoking the assertAll() method of the SoftAssertions class. This method runs all specified assertions and reports assertion failures after these assertions have been run.

After we have written the required soft assertions, the source code of our test class looks as follows:

import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@DisplayName("Collect all error messages before reporting them")
class SoftAssertionsTest {

    private static final String FIRST_NAME = "Jane";
    private static final String LAST_NAME = "Doe";

    private Person person;

    @BeforeEach
    void createPerson() {
        person = new Person();
        person.setFirstName(FIRST_NAME);
        person.setLastName(LAST_NAME);
    }

    @Test
    @DisplayName("Should have the correct name")
    void shouldHaveCorrectName() {
        SoftAssertions softAssertions = new SoftAssertions();

        softAssertions.assertThat(person.getFirstName())
                .overridingErrorMessage(
                        "Expected the first name to be: %s but it was: %s",
                        FIRST_NAME,
                        person.getFirstName()
                )
                .isEqualTo(FIRST_NAME);
        softAssertions.assertThat(person.getLastName())
                .overridingErrorMessage(
                        "Expected the last name to be: %s but it was: %s",
                        LAST_NAME,
                        person.getLastName()
                )
                .isEqualTo(LAST_NAME);

        softAssertions.assertAll();
    }
}
If you want to get access to up-to-date material which is essentially a better version of my JUnit 5 tutorial, you should take a look at my Introduction to JUnit 5 course.

Additional Reading:

We can now write basic assertions with AssertJ, provide a custom error message that's shown when an assertion fails, and write soft assertions with AssertJ.

Let’s summarize what we learned from this blog post.

Summary

This blog post has taught us five things:

  • Before we can write assertions with AssertJ, we have to ensure that the assertj-core dependency is found from the classpath.
  • We can write assertions with AssertJ by using the static methods of the org.assertj.core.api.Assertions class.
  • If we want override the description part of the error message that's shown when an assertion fails, we have to use either the as() or describedAs() methods of the Descriptable interface.
  • If we want to override the entire error message that's shown when an assertion fails, we have to invoke the overridingErrorMessage() method of the AbstractAssert class.
  • We can write soft assertions with AssertJ by using the org.assertj.core.api.SoftAssertions class.

P.S. You can get the example application of this blog post from Github.

5 comments… add one
  • L Sep 30, 2019 @ 23:02

    This was really useful, thank you!

    • Petri Oct 22, 2019 @ 18:33

      You are welcome!

  • cedric774 Aug 31, 2023 @ 16:39

    Perfect explanations :)

    • Petri Aug 31, 2023 @ 21:22

      Thank you! I am happy to hear that this blog post was useful to you.

Leave a Reply