https://drive.google.com/file/d/16UXXGbPy61zsl5XK9IR90zKzXtvwyt6f/view?usp=sharing
📌 이메일 발송
✅ EmailService 비즈니스 로직 V1
/**
* 이메일 발송 기능을 담당하는 서비스 클래스입니다.
* Spring Boot의 JavaMailSender를 이용해 실제 이메일을 발송하며,
* 실패 시 커스텀 예외를 발생시킵니다.
*
* 메일 발송 성공/실패 여부는 로그로 남기고, 국제화 메시지를 통해 에러 메시지를 구성합니다.
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class EmailService {
// Spring Boot가 자동 설정한 JavaMailSender 빈을 주입받습니다.
private final JavaMailSender mailSender;
// 국제화 메시지 처리용 MessageSource (messages.properties 등에서 메시지 읽기)
private final MessageSource messageSource;
// 발신자 이메일 주소 (application.yml의 spring.mail.username 값으로 설정됨)
@Value("${spring.mail.username}")
private String fromEmail;
/**
* 이메일을 발송하는 메서드입니다.
* 내부적으로 MimeMessage를 생성 후 JavaMailSender로 전송합니다.
*
* @param toEmail 수신자 이메일 주소
* @param subject 이메일 제목
* @param body 이메일 본문 (HTML 허용)
* @throws CustomException 이메일 발송 실패 시 사용자 정의 예외 발생
*/
@Transactional
public void sendEmail(String toEmail, String subject, String body) {
MimeMessage message = createMimeMessage(toEmail, subject, body);
try {
mailSender.send(message);
log.info("Email successfully sent to {}", toEmail);
} catch (MailException e) {
log.error("Failed to send email to {}", toEmail, e);
// 국제화 메시지 사용 + 커스텀 예외로 변환하여 핸들러로 전달
throw new CustomException(
FAIL_500.code(),
messageSource.getMessage("mail.send.fail", null, Locale.getDefault()),
HttpStatus.INTERNAL_SERVER_ERROR
);
}
}
/**
* MimeMessage 객체를 생성하고 필요한 설정(From, To, Subject, Body)을 구성합니다.
*
* @param to 수신자 이메일 주소
* @param subject 이메일 제목
* @param body 이메일 본문 (HTML 포함 가능)
* @return 구성된 MimeMessage 객체
* @throws CustomException 메시지 구성 중 에러가 발생할 경우
*/
private MimeMessage createMimeMessage(String to, String subject, String body) {
try {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(fromEmail); // 설정된 발신자 이메일
helper.setTo(to); // 수신자 이메일
helper.setSubject(subject); // 메일 제목
helper.setText(body, true); // HTML 지원 본문
return message;
} catch (MessagingException e) {
log.error("Failed to construct email message", e);
// 국제화 메시지 사용 + 커스텀 예외로 변환
throw new CustomException(
FAIL_500.code(),
messageSource.getMessage("mail.message.create.failed", null, Locale.getDefault()),
HttpStatus.INTERNAL_SERVER_ERROR
);
}
}
}
✅ EmailService Junit
@Test
void passwordResetEmailSend() {
//given
String toEmail = "[email protected]";
String subject = "5월 부터 지원하자!";
String body = "가자!!!";
// when & then
try {
emailService.sendEmail(toEmail, subject, body);
log.info("발송 성공!!");
} catch (Exception e) {
Assertions.fail("발송 실패");
}
}
🚀 결과
하지만 이 junit은 통합 테스트지 내가 추구하는 TDD가 아니다. 🙅
TDD 스타일로 다시 한번 코딩 🧑💻
package com.parker.service.api.v1.user.service;
import com.parker.common.service.EmailService;
import jakarta.mail.internet.MimeMessage;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.MessageSource;
import org.springframework.mail.javamail.JavaMailSender;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.times;
/**
* EmailService의 단위 테스트 클래스입니다.
* 외부 의존성(JavaMailSender, MessageSource)을 Mock으로 대체하고,
* 메일 발송이 실제로 호출되는지를 검증합니다.
*/
@ExtendWith(MockitoExtension.class) // Mockito 기반 테스트 확장 활성화 (JUnit5용)
class EmailServiceTest {
@Mock
JavaMailSender javaMailSender; // 이메일 발송 컴포넌트를 Mock 객체로 대체
@Mock
MessageSource messageSource; // 다국어 메시지 처리를 위한 MessageSource도 Mock 처리
@InjectMocks
EmailService emailService; // 위의 Mock들을 주입받은 실제 테스트 대상 EmailService
/**
* 이메일을 발송했을 때, JavaMailSender.send() 메서드가 정확히 1번 호출되었는지를 검증합니다.
*/
@Test
void sendEmail_ShouldInvokeMailSender() {
// given - 사전 조건 설정
// JavaMailSender가 반환할 MimeMessage 객체를 직접 모킹
MimeMessage mockMessage = mock(MimeMessage.class);
when(javaMailSender.createMimeMessage()).thenReturn(mockMessage);
// when - 실제 서비스 메서드 호출
emailService.sendEmail("[email protected]", "제목", "내용");
// then - 결과 검증
// JavaMailSender의 send() 메서드가 정확히 1번 호출되었는지 검증
verify(javaMailSender, times(1)).send(mockMessage);
}
}