Skip to content

Instantly share code, notes, and snippets.

@rponte
Last active December 17, 2024 14:09
Show Gist options
  • 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> {
}
@B-A-N-D-I-T
Copy link

Thank you very much <3

@rponte
Copy link
Author

rponte commented Dec 17, 2024

If you want to test ONLY the bean validation annotations directly from the entities, DTOs, and domain model (without repositories, services, or controllers), you should follow this other approach, that's faster and simpler!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment