JUnit 5 testing patterns with Mockito and AssertJ
Practical testing patterns for Java services and APIs. Prefer readable tests over clever tests.
Read ONLY files relevant to the request! Use the content map to focus.
| File | Description | When to Read |
|---|---|---|
junit-basics.md |
@Test, assertions, lifecycle | Unit test setup |
parameterized.md |
@ParameterizedTest, sources | Data-driven tests |
mockito.md |
@Mock, @InjectMocks, stubbing | Isolated unit tests |
assertj.md |
Fluent assertions, collections | Readable assertions |
spring-slices.md |
@WebMvcTest, @DataJpaTest | Framework-level tests |
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void findById_whenExists_returnsUser() {
var user = User.create("u1", "dev@example.com");
when(userRepository.findById("u1")).thenReturn(Optional.of(user));
var result = userService.findById("u1");
assertThat(result).isPresent();
assertThat(result.get().getEmail()).isEqualTo("dev@example.com");
}
}
@ParameterizedTest
@ValueSource(strings = {"a@b.com", "user@company.io"})
void validateEmail_acceptsValid(String email) {
var result = EmailValidator.isValid(email);
assertThat(result).isTrue();
}
@ParameterizedTest
@CsvSource({
"PENDING, true",
"ACTIVE, false"
})
void isInactive_flagsStatus(String status, boolean expected) {
assertThat(UserStatus.valueOf(status).isInactive()).isEqualTo(expected);
}
@Test
void createUser_savesEntity() {
var request = new CreateUserRequest("dev@example.com", "Dev");
when(userRepository.existsByEmail(request.email())).thenReturn(false);
userService.create(request);
verify(userRepository).save(any(User.class));
}
@Test
void updateUser_throwsWhenMissing() {
when(userRepository.findById("missing")).thenReturn(Optional.empty());
assertThatThrownBy(() -> userService.update("missing", new UpdateUserRequest("name")))
.isInstanceOf(ResourceNotFoundException.class)
.hasMessageContaining("User");
}
assertThat(users)
.hasSize(3)
.extracting(User::getEmail)
.contains("dev@example.com");
assertThat(response)
.returns(HttpStatus.CREATED, ResponseEntity::getStatusCode)
.extracting(ResponseEntity::getBody)
.isNotNull();
@Nested
class CreateUserTests {
@Test
void rejectsDuplicateEmail() { }
@Test
void hashesPassword() { }
}
@BeforeEach
void setup() {
// Use only for shared, simple setup
}
| Anti-Pattern | Why Bad | Better Approach |
|---|---|---|
| Testing private methods | Refactoring pain | Test public behavior |
| Mocking everything | Brittle tests | Mock only boundaries |
| Multiple behaviors per test | Hard to debug | Split into focused tests |
| Asserting via logs | Not deterministic | Assert on outcomes |
| Need | Skill |
|---|---|
| Java patterns | @[skills/java-expert] |
| Build tools | @[skills/maven-gradle] |