Skip to content

Instantly share code, notes, and snippets.

@rponte
Last active October 17, 2023 14:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rponte/c45456e58c323b62a8180485f3771b46 to your computer and use it in GitHub Desktop.
Save rponte/c45456e58c323b62a8180485f3771b46 to your computer and use it in GitHub Desktop.
AssertJ: Example of a jUnit5 test for asserting Bean Validation errors thrown by Spring Boot Repository
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.transaction.TransactionSystemException;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Optional;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.InstanceOfAssertFactories.iterable;
import static org.assertj.core.groups.Tuple.tuple;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@ActiveProfiles("test")
class BookRepositoryTest {
@Autowired
private BookRepository repository;
@BeforeEach
void setUp() {
repository.deleteAll();
}
@Test
@DisplayName("must save a book")
void t1() {
// scenario
Book book = new Book("9788550800653", "Domain Drive Design", "DDD");
// action
repository.save(book);
// validation
assertEquals(1, repository.findAll().size());
}
@Test
@DisplayName("should not save a book with invalid parameters")
void t2() {
// scenario
Book book = new Book("97885-invalid", "a".repeat(121), "");
// action and validation
// alternative #1
Throwable springException = catchException(() -> repository.save(book)); // or assertThrows() from jUnit
assertThat(springException)
.isInstanceOf(TransactionSystemException.class)
.hasRootCauseInstanceOf(ConstraintViolationException.class)
.getRootCause()
.extracting("constraintViolations", as(iterable(ConstraintViolation.class)))
.extracting(
t -> t.getPropertyPath().toString(),
ConstraintViolation::getMessage
)
.containsExactlyInAnyOrder(
tuple("isbn", "invalid ISBN"),
tuple("title", "size must be between 0 and 120"),
tuple("description", "must not be blank")
)
;
// alternative #2 (my favorite one!)
assertThatThrownBy(() -> { repository.save(book); })
.isInstanceOf(TransactionSystemException.class)
.hasRootCauseInstanceOf(ConstraintViolationException.class)
.getRootCause()
.extracting("constraintViolations", as(iterable(ConstraintViolation.class)))
.extracting(
t -> t.getPropertyPath().toString(),
ConstraintViolation::getMessage
)
.containsExactlyInAnyOrder(
tuple("isbn", "invalid ISBN"),
tuple("title", "size must be between 0 and 120"),
tuple("description", "must not be blank")
)
;
// alternative #3
assertThatCode(() -> { repository.save(book); })
.isInstanceOf(TransactionSystemException.class)
.hasRootCauseInstanceOf(ConstraintViolationException.class)
.getRootCause()
.extracting("constraintViolations", as(iterable(ConstraintViolation.class)))
.extracting(
t -> t.getPropertyPath().toString(),
ConstraintViolation::getMessage
)
.containsExactlyInAnyOrder(
tuple("isbn", "invalid ISBN"),
tuple("title", "size must be between 0 and 120"),
tuple("description", "must not be blank")
)
;
// alternative #4
assertThatExceptionOfType(TransactionSystemException.class)
.isThrownBy(() -> repository.save(book))
.withRootCauseInstanceOf(ConstraintViolationException.class)
.havingRootCause()
.extracting("constraintViolations", as(iterable(ConstraintViolation.class)))
.extracting(
t -> t.getPropertyPath().toString(),
ConstraintViolation::getMessage
)
.containsExactlyInAnyOrder(
tuple("isbn", "invalid ISBN"),
tuple("title", "size must be between 0 and 120"),
tuple("description", "must not be blank")
)
;
}
}
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.ISBN;
import java.util.Objects;
import static org.hibernate.validator.constraints.ISBN.Type.ISBN_13;
@Entity
@Table(
uniqueConstraints = {
@UniqueConstraint(name = "uk_isbn", columnNames = "isbn")
}
)
public class Book {
@Id
@GeneratedValue
private Long id;
@NotBlank
@ISBN(type = ISBN_13)
@Column(nullable = false, length = 13)
private String isbn;
@NotBlank
@Size(max = 120)
@Column(nullable = false, length = 120)
private String title;
@NotBlank
@Size(max = 4000)
@Column(nullable = false, length = 4000)
private String description;
/**
* @deprecated Exclusive use of Hibernate
*/
@Deprecated
public Book() {}
public Book(String isbn, String title, String description) {
this.isbn = isbn;
this.title = title;
this.description = description;
}
public Long getId() {
return id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return Objects.equals(isbn, book.isbn);
}
@Override
public int hashCode() {
return Objects.hash(isbn);
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
", isbn='" + isbn + '\'' +
", title='" + title + '\'' +
", description='" + description + '\'' +
'}';
}
}
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@Repository
@Transactional(readOnly = true)
public interface BookRepository extends JpaRepository<Book, Long> {
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment