Wim Deblauwe

AssertJ test cause of exception

May 8, 2020 · 2 min read spring-boot testcontainers assertj

AssertJ 3.16.0 has just been released. It has a feature I contributed to allow testing the (root) cause of an exception.

This blog post will show how to use this to our advantage.

Let’s get started with a simple Spring Boot application using Spring Data JPA.

We will have 3 entities: Book, Song and User

A User can have exactly 1 favorite book and 1 favorite song. The book and the song have to be in the database already before the user can make it a favorite of his.

Check the sources on Github for the full code.

This is how the User looks like:

@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Song favoriteSong;

    @ManyToOne(fetch = FetchType.LAZY)
    private Book favoriteBook;

    ...
}

The code also has BookRepository, SongRepository and UserRepository.

The test we will focus on is the UserRepositoryTest:

@Test
void testUnableToSaveUserIfBookNotInDatabase() {
    Book book = new Book();
    book.setTitle("AssertJ in action");

    User user = new User();
    user.setFavoriteBook(book);

    assertThatExceptionOfType(IllegalStateException.class)
            .isThrownBy(() -> {
                repository.save(user);
                entityManager.flush();
            })
            .havingCause()
            .withMessageMatching(".*object references an unsaved transient instance.*User.favoriteBook.*");
}

What we test here is that if a User has a favorite Book that is not yet saved in the database, we get an IllegalStateException. Not only that, but we also check that there is a cause (via havingCause()) and that this cause should have a particular message where the User.favoriteBook field is present.

A similar test can be done if a Song is not yet saved in the database:

 @Test
void testUnableToSaveUserIfSongNotInDatabase() {
    Song song = new Song();
    song.setTitle("Bee Gees - Stayin' Inside");
    User user = new User();
    user.setFavoriteSong(song);
    assertThatExceptionOfType(IllegalStateException.class)
            .isThrownBy(() -> {
                repository.save(user);
                entityManager.flush();
            })
            .havingCause()
            .withMessageMatching(".*object references an unsaved transient instance.*User.favoriteSong.*");
}

See Checking cause and root cause in the AssertJ documentation for more information about this handy function.