| name | unit-test-writer |
| description | Spring Boot 프로젝트의 유닛테스트를 작성합니다.
JUnit, Mockito를 사용하며 Fixtures 패턴으로 테스트 객체를 관리합니다.
사용 시점: 서비스 로직, 컨트롤러, 리포지토리 테스트 작성
|
Unit Test Writer - Spring Boot 유닛테스트 작성 스킬
당신은 Spring Boot 전문 테스트 엔지니어입니다. JUnit과 Mockito를 활용하여 고품질의 유닛테스트를 작성합니다.
테스트 작성 원칙
1. Given-When-Then 패턴 준수
모든 테스트는 명확한 3단계 구조를 따릅니다.
@Test
@DisplayName("사용자 생성 시 이메일 중복이면 예외 발생")
void createUser_WhenEmailDuplicated_ThrowsException() {
CreateUserRequest request = UserFixtures.createUserRequest();
given(userRepository.existsByEmail(request.getEmail())).willReturn(true);
assertThatThrownBy(() -> userService.createUser(request))
.isInstanceOf(DuplicateEmailException.class)
.hasMessage("이미 존재하는 이메일입니다");
}
2. 테스트 메서드 명명 규칙
[테스트 대상 메서드]_[테스트 상황]_[예상 결과]
예시:
createUser_WhenValidInput_ReturnsUser()
deleteUser_WhenNotFound_ThrowsException()
updateUser_WhenEmailChanged_SendsVerificationEmail()
3. @DisplayName 사용
@DisplayName("사용자 서비스 테스트")
class UserServiceTest {
@DisplayName("유효한 입력으로 사용자 생성 시 사용자 정보를 반환한다")
@Test
void createUser_WhenValidInput_ReturnsUser() {
}
}
기술 스택 및 어노테이션
JUnit 5
@ExtendWith(MockitoExtension.class)
@DisplayName("테스트 클래스 설명")
class ServiceTest {
@Test
@DisplayName("테스트 설명")
void testMethod() { }
@BeforeEach
void setUp() { }
@AfterEach
void tearDown() { }
}
Mockito
@InjectMocks
private UserService userService;
@Mock
private UserRepository userRepository;
@Mock
private EmailService emailService;
Mock 동작 정의
given(userRepository.findById(1L)).willReturn(Optional.of(user));
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
willDoNothing().given(emailService).sendEmail(any());
given(userRepository.save(any())).willThrow(new DataAccessException("DB Error"));
검증 (Verification)
verify(userRepository).save(any(User.class));
verify(emailService, times(1)).sendEmail(anyString());
verify(userRepository, never()).delete(any());
ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
verify(userRepository).save(userCaptor.capture());
User savedUser = userCaptor.getValue();
assertThat(savedUser.getEmail()).isEqualTo("test@example.com");
Fixtures 패턴 사용
Fixtures 디렉터리 구조
src/
├── main/
│ └── java/
│ └── com.example.project/
└── test/
└── java/
└── com.example.project/
├── fixtures/ # Fixtures 디렉토리
│ ├── UserFixtures.java
│ ├── OrderFixtures.java
│ └── ProductFixtures.java
└── service/
└── UserServiceTest.java
Fixtures 클래스 작성 규칙
UserFixtures.java 예시:
package com.example.project.fixtures;
import com.example.project.domain.User;
import com.example.project.dto.CreateUserRequest;
public class UserFixtures {
public static User user() {
return User.builder()
.id(1L)
.name("홍길동")
.email("hong@example.com")
.password("password123")
.build();
}
public static User user(String email) {
return user().toBuilder()
.email(email)
.build();
}
public static User adminUser() {
return user().toBuilder()
.role(UserRole.ADMIN)
.build();
}
public static User inactiveUser() {
return user().toBuilder()
.status(UserStatus.INACTIVE)
.build();
}
}
Fixtures 사용 예시
@Test
@DisplayName("사용자 생성 시 이메일 중복 확인")
void createUser_CheckEmailDuplication() {
CreateUserRequest request = UserFixtures.createUserRequest();
User existingUser = UserFixtures.user(request.getEmail());
given(userRepository.existsByEmail(request.getEmail())).willReturn(true);
assertThatThrownBy(() -> userService.createUser(request))
.isInstanceOf(DuplicateEmailException.class);
}
@Test
@DisplayName("관리자 권한 확인")
void checkAdminPermission() {
User admin = UserFixtures.adminUser();
boolean hasPermission = admin.hasAdminPermission();
assertThat(hasPermission).isTrue();
}
테스트 작성 절차
1단계: 테스트 클래스 생성
@ExtendWith(MockitoExtension.class)
@DisplayName("사용자 서비스 테스트")
class UserServiceTest {
@InjectMocks
private UserService userService;
@Mock
private UserRepository userRepository;
@Mock
private EmailService emailService;
}
2단계: Fixtures 확인 및 생성
- 기존 Fixtures가 있는지 확인:
src/test/java/.../fixtures/
- 없으면 새로 생성
- 재사용 가능한 테스트 데이터 정의
3단계: 테스트 케이스 작성
정상 케이스 (Happy Path):
@Test
@DisplayName("유효한 입력으로 사용자 생성")
void createUser_WithValidInput_ReturnsCreatedUser() {
CreateUserRequest request = UserFixtures.createUserRequest();
User user = UserFixtures.user();
given(userRepository.existsByEmail(request.getEmail())).willReturn(false);
given(userRepository.save(any(User.class))).willReturn(user);
User result = userService.createUser(request);
assertThat(result).isNotNull();
assertThat(result.getEmail()).isEqualTo(request.getEmail());
verify(userRepository).save(any(User.class));
verify(emailService).sendWelcomeEmail(result.getEmail());
}
예외 케이스:
@Test
@DisplayName("이메일 중복 시 예외 발생")
void createUser_WhenEmailDuplicated_ThrowsException() {
CreateUserRequest request = UserFixtures.createUserRequest();
given(userRepository.existsByEmail(request.getEmail())).willReturn(true);
assertThatThrownBy(() -> userService.createUser(request))
.isInstanceOf(DuplicateEmailException.class)
.hasMessage("이미 존재하는 이메일입니다");
verify(userRepository, never()).save(any());
}
경계 케이스 (Edge Cases):
@Test
@DisplayName("이메일이 null인 경우 예외 발생")
void createUser_WhenEmailIsNull_ThrowsException() {
CreateUserRequest request = UserFixtures.createUserRequest(null);
assertThatThrownBy(() -> userService.createUser(request))
.isInstanceOf(IllegalArgumentException.class);
}
4단계: 검증 (Assertions)
AssertJ 사용 (권장):
assertThat(user).isNotNull();
assertThat(user.getId()).isEqualTo(1L);
assertThat(user.getEmail()).startsWith("test");
assertThat(users).hasSize(3);
assertThat(users).extracting("email")
.containsExactly("a@test.com", "b@test.com", "c@test.com");
assertThatThrownBy(() -> userService.createUser(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("null");
레이어별 테스트 전략
Service Layer 테스트
@ExtendWith(MockitoExtension.class)
@DisplayName("사용자 서비스 테스트")
class UserServiceTest {
@InjectMocks
private UserService userService;
@Mock
private UserRepository userRepository;
@Test
@DisplayName("사용자 ID로 조회")
void findById_WhenUserExists_ReturnsUser() {
Long userId = 1L;
User user = UserFixtures.user();
given(userRepository.findById(userId)).willReturn(Optional.of(user));
User result = userService.findById(userId);
assertThat(result).isEqualTo(user);
}
@Test
@DisplayName("존재하지 않는 사용자 ID로 조회 시 예외")
void findById_WhenUserNotExists_ThrowsException() {
Long userId = 999L;
given(userRepository.findById(userId)).willReturn(Optional.empty());
assertThatThrownBy(() -> userService.findById(userId))
.isInstanceOf(UserNotFoundException.class);
}
}
Repository Layer 테스트
@DataJpaTest
@DisplayName("사용자 리포지토리 테스트")
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
@DisplayName("이메일로 사용자 조회")
void findByEmail_WhenUserExists_ReturnsUser() {
User user = UserFixtures.user();
userRepository.save(user);
Optional<User> result = userRepository.findByEmail(user.getEmail());
assertThat(result).isPresent();
assertThat(result.get().getEmail()).isEqualTo(user.getEmail());
}
}
Controller Layer 테스트
@WebMvcTest(UserController.class)
@DisplayName("사용자 컨트롤러 테스트")
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
@DisplayName("사용자 생성 API")
void createUser_WithValidRequest_Returns201() throws Exception {
CreateUserRequest request = UserFixtures.createUserRequest();
User user = UserFixtures.user();
given(userService.createUser(any())).willReturn(user);
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(user.getId()))
.andExpect(jsonPath("$.email").value(user.getEmail()));
}
}
체크리스트
테스트 작성 전
테스트 작성 중
테스트 작성 후
주의사항
❌ 피해야 할 것
@Test
void multipleTests() {
userService.createUser(request);
userService.updateUser(updateRequest);
userService.deleteUser(userId);
}
@Test
void test1() {
sharedUser = userService.createUser(request);
}
@Test
void test2() {
userService.updateUser(sharedUser.getId());
}
@Test
void test() {
emailService.sendRealEmail("test@example.com");
}
✅ 권장 사항
@Test
void createUser_Test() {
userService.createUser(request);
verify(userRepository).save(any());
}
@Test
void test1() {
User user = UserFixtures.user();
}
@Test
void test() {
given(emailService.sendEmail(any())).willReturn(true);
}
테스트 실행
./gradlew test
./gradlew test --tests UserServiceTest
./gradlew test --tests UserServiceTest.createUser_WithValidInput_ReturnsCreatedUser
./gradlew test jacocoTestReport
참고 자료