Testing with JUnit 5, Mockito, AssertJ
Practical testing patterns for Java services and APIs. Prefer readable tests over clever tests.
Selective Reading Rule
Read ONLY files relevant to the request! Use the content map to focus.
Content Map
| 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 |
Core Patterns
1. JUnit 5 Structure
java
@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");
}
}
2. Parameterized Tests
java
@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);
}
3. Mockito Stubbing and Verification
java
@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");
}
4. AssertJ for Readability
java
assertThat(users)
.hasSize(3)
.extracting(User::getEmail)
.contains("dev@example.com");
assertThat(response)
.returns(HttpStatus.CREATED, ResponseEntity::getStatusCode)
.extracting(ResponseEntity::getBody)
.isNotNull();
5. Test Lifecycle and Organization
java
@Nested
class CreateUserTests {
@Test
void rejectsDuplicateEmail() { }
@Test
void hashesPassword() { }
}
@BeforeEach
void setup() {
// Use only for shared, simple setup
}
Decision Checklist
- • Test name explains behavior? (when/then or should format)
- • Only one reason to fail?
- • Mocks only for external dependencies?
- • Assertions are readable?
- • Parameterized test when data-driven?
Anti-Patterns
| 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 |
Related Skills
| Need | Skill |
|---|---|
| Java patterns | @[skills/java-expert] |
| Build tools | @[skills/maven-gradle] |