SKILL.md
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
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void findById_whenExists_returnsUser() {
var user = User.create("u1", "[email protected]");
when(userRepository.findById("u1")).thenReturn(Optional.of(user));
var result = userService.findById("u1");
assertThat(result).isPresent();
assertThat(result.get().getEmail()).isEqualTo("[email protected]");
}
}
2. Parameterized Tests
@ParameterizedTest
@ValueSource(strings = {"[email protected]", "[email protected]"})
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
@Test
void createUser_savesEntity() {
var request = new CreateUserRequest("[email protected]", "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
assertThat(users)
.hasSize(3)
.extracting(User::getEmail)
.contains("[email protected]");
assertThat(response)
.returns(HttpStatus.CREATED, ResponseEntity::getStatusCode)
.extracting(ResponseEntity::getBody)
.isNotNull();
5. Test Lifecycle and Organization
@Nested
class CreateUserTests {
@Test
void rejectsDuplicateEmail() { }
@Test
void hashesPassword() { }
}
@BeforeEach
void setup() {
// Use only for shared, simple setup
}
Decision Checklist
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] |