16.2 Writing Unit Tests (JUnit5 + AssertJ)
1. JUnit5 Key Annotations
@DisplayName("UserService Unit Tests")
class UserServiceTest {
@Test
@DisplayName("Registering a duplicate email throws an exception.")
void duplicate_email_throws_exception() {
// Given
UserService userService = new UserService(new FakeUserRepository());
// When / Then
assertThatThrownBy(() -> userService.register("duplicate@test.com", "password"))
.isInstanceOf(DuplicateEmailException.class)
.hasMessageContaining("Email already in use");
}
@ParameterizedTest
@ValueSource(strings = {"invalid", "no-at-sign", ""})
@DisplayName("Invalid email formats throw an exception.")
void invalid_email_format_throws(String invalidEmail) {
assertThatThrownBy(() -> new Email(invalidEmail))
.isInstanceOf(IllegalArgumentException.class);
}
@Nested
@DisplayName("Point deduction tests")
class PointDeductTest {
@Test @DisplayName("Deduction succeeds when points are sufficient.") void sufficient_points() { ... }
@Test @DisplayName("Deduction fails when points are insufficient.") void insufficient_points() { ... }
}
}
2. AssertJ for Readable Assertions
AssertJ provides far more expressive and readable assertions than JUnit5's built-in Assertions.
assertThat(result).isEqualTo(expected);
assertThat(list).hasSize(3).contains("A", "B");
assertThat(user.getName()).isNotNull().startsWith("John");
assertThat(price).isGreaterThan(0).isLessThanOrEqualTo(100_000);
// Exception assertions
assertThatThrownBy(() -> service.dangerousMethod())
.isInstanceOf(CustomException.class)
.hasMessage("Error message");
// Optional assertions
assertThat(optUser).isPresent().hasValueSatisfying(u ->
assertThat(u.getEmail()).isEqualTo("user@example.com")
);