Spring/03. Service
02. Service Test
by 989898
2025. 2. 16.
1. 서비스 코드 (MemberService)
1.1 주요 역할
- 비즈니스 로직 처리:
- 회원 등록, 수정, 조회 등 핵심 로직을 처리.
- 외부 의존성 주입:
- MemberRepository: 데이터베이스와 상호작용.
- PasswordEncoder: 비밀번호 암호화.
1.2 회원 등록 메서드 (registerMember)
public MemberResponse registerMember(MemberRegisterRequest memberRegisterRequest) {
// 이메일 중복 체크
if (memberRepository.existsByMbEmail(memberRegisterRequest.getMbEmail())) {
throw new ConflictException("이미 사용 중인 이메일입니다.");
}
// 휴대폰 번호 중복 체크
if (memberRepository.existsByMbMobile(memberRegisterRequest.getMbMobile())) {
throw new ConflictException("이미 사용 중인 모바일 번호입니다.");
}
// 비밀번호 암호화 및 회원 객체 생성
Member member = new Member(
memberRegisterRequest.getMbEmail(),
memberRegisterRequest.getMbName(),
passwordEncoder.encode(memberRegisterRequest.getPassword()),
memberRegisterRequest.getMbMobile()
);
memberRepository.save(member);
// DTO 반환
return new MemberResponse(
member.getMbNo(),
member.getMbEmail(),
member.getMbName(),
member.getMbMobile(),
member.getCreatedAt()
);
}
2. 테스트 코드 작성
2.1 테스트 목적
- 서비스 메서드가 예상대로 동작하는지 확인.
- 외부 의존성(Mock 객체)을 통해 독립적으로 테스트 수행.
2.2 회원 등록 테스트
@Test
@DisplayName("회원등록")
void registerMember() {
// 1. 테스트용 요청 객체 생성
MemberRegisterRequest request = new MemberRegisterRequest(
"marco@nhnacademy.com", "마르코", "a123456789!", "01012345678"
);
// 2. Mock 설정
Mockito.when(memberRepository.existsByMbEmail(Mockito.anyString())).thenReturn(false);
Mockito.when(memberRepository.existsByMbMobile(Mockito.anyString())).thenReturn(false);
// save 호출 시 동작 정의 (mbNo를 1로 설정)
Mockito.doAnswer(invocation -> {
Member paramMember = invocation.getArgument(0);
Field field = Member.class.getDeclaredField("mbNo");
field.setAccessible(true);
field.set(paramMember, 1L);
return null;
}).when(memberRepository).save(Mockito.any(Member.class));
// 3. 서비스 호출
MemberResponse response = memberService.registerMember(request);
// 4. 결과 검증
Assertions.assertAll(
() -> Assertions.assertEquals(1L, response.getMbNo()),
() -> Assertions.assertEquals("marco@nhnacademy.com", response.getMbEmail()),
() -> Assertions.assertEquals("마르코", response.getMbName()),
() -> Assertions.assertEquals("01012345678", response.getMbMobile()),
() -> Assertions.assertNotNull(response.getCreatedAt())
);
// 5. Mock 호출 검증
Mockito.verify(memberRepository, Mockito.times(1)).existsByMbEmail(Mockito.anyString());
Mockito.verify(memberRepository, Mockito.times(1)).existsByMbMobile(Mockito.anyString());
Mockito.verify(memberRepository, Mockito.times(1)).save(Mockito.any(Member.class));
}
2.3 예외 처리 테스트
@Test
@DisplayName("회원등록 - 이메일중복")
void registerMember_exception_case1() {
// 1. Mock 설정: 이메일 중복 체크에서 true 반환
Mockito.when(memberRepository.existsByMbEmail(Mockito.anyString())).thenReturn(true);
// 2. 서비스 호출 및 예외 검증
Assertions.assertThrows(ConflictException.class, () -> {
memberService.registerMember(new MemberRegisterRequest(
"marco@nhnacademy.com", "마르코", "a123456789!", "01012345678"
));
});
// 3. Mock 호출 검증: save는 호출되지 않아야 함
Mockito.verify(memberRepository, Mockito.times(1)).existsByMbEmail(Mockito.anyString());
Mockito.verify(memberRepository, Mockito.never()).save(Mockito.any(Member.class));
}
3. 테스트 코드와 서비스 코드의 연결
3.1 Mocking
- 외부 의존성을 제거하고 가짜(Mock) 객체를 사용하여 테스트.
- 주요 메서드:
- Mockito.when(): 특정 메서드 호출 시 반환값 설정.
- Mockito.doAnswer(): 특정 메서드 호출 시 동작 정의.
- Mockito.verify(): 특정 메서드 호출 여부 및 횟수 검증.
3.2 Assertions
- 결과 값이 예상 값과 일치하는지 확인.
- 주요 메서드:
- Assertions.assertEquals(expected, actual): 값 비교.
- Assertions.assertThrows(exceptionClass, executable): 예외 발생 여부 확인.
- Assertions.assertAll(): 여러 검증을 그룹화하여 실행.
3.3 서비스 메서드 호출
MemberResponse response = memberService.registerMember(request);
4. 주요 포인트 요약
| 항목 |
설명 |
| Mock 설정 |
Mockito.when()으로 외부 의존성(Mock 객체)의 동작을 정의합니다. |
| 서비스 호출 |
memberService.registerMember()와 같은 실제 로직을 실행합니다. |
| 결과 검증 |
Assertions를 사용해 반환된 결과가 예상과 일치하는지 확인합니다. |
| 예외 처리 테스트 |
Assertions.assertThrows()로 예외 발생 여부를 확인합니다. |
| Mock 검증 |
Mockito.verify()로 Mock 객체의 특정 메서드가 적절히 호출되었는지 확인합니다. |
5. 간단한 흐름 요약
- 회원 등록 성공 테스트:
- Mock 설정 → 서비스 호출 → 결과 검증 → Mock 호출 검증.
- 회원 등록 실패 테스트 (예외 처리):
- Mock 설정 (중복 조건) → 서비스 호출 → 예외 발생 확인 → Mock 호출 검증.
6. 결론
- 이 테스트 코드는 서비스 계층의 비즈니스 로직을 철저히 검증하며, 외부 의존성을 제거하기 위해 Mock 객체를 활용합니다.
- 이를 통해 코드의 안정성과 유지보수성을 높이고, 다양한 예외 상황에 대한 방어적 코드를 보장할 수 있습니다.