빙응의 공부 블로그

[Spring]단위 테스트 정리하기 Mockito 실습 (3/3) 본문

Spring/개인공부_이론

[Spring]단위 테스트 정리하기 Mockito 실습 (3/3)

빙응이 2024. 10. 18. 15:42

[Spring]단위 테스트 정리하기 JUnit(1/3) (tistory.com)

[Spring]단위 테스트 정리하기 Mockito (2/3) (tistory.com)

📝실습할 코드 

실습 코드는 현재 진행하는 프로젝트에서 가져왔고
매우 어려운 부분을 가져왔습니다..

 

 

해당 코드는 프로젝트에서 가져온 학교 인증 비즈니스 로직입니다. 

@Service
@RequiredArgsConstructor
@Slf4j
public class UniversityVerificationService {
    private final JavaMailSender javaMailSender;
    private final UniversityVerificationRepository universityVerificationRepository;

    @Value("${spring.mail.username}")
    private String serviceName;

    private final MemberRepository memberRepository;

    // 6자리 난수 생성
    private String makeRandomNum() {
        Random r = new Random();
        StringBuilder randomNumber = new StringBuilder();
        for (int i = 0; i < 6; i++) {
            randomNumber.append(r.nextInt(10));
        }
        return randomNumber.toString();
    }

    /* 이메일 전송 */
    public void mailSend(String toMail, String title, String content) {
        MimeMessage message = javaMailSender.createMimeMessage();
        try {
            message.setFrom(serviceName); // 발신자 이메일 설정
            message.setRecipients(MimeMessage.RecipientType.TO, toMail); // 수신자 이메일 설정
            message.setSubject(title); // 이메일 제목 설정

            // 이메일 본문 설정 (HTML 형식)
            message.setText(content, "UTF-8", "html");

            // 이메일 전송
            javaMailSender.send(message);
        } catch (MessagingException e) {
            throw new CustomException(MailErrorCode.FAILED_MAIL_SEND);
        }
    }

    /* 이메일 작성 및 인증 코드 저장 */
    public void sendVerificationEmail(String email) {
        String authNumber = makeRandomNum(); // 난수 생성
        String title = "회원 가입을 위한 이메일입니다!";
        String content = "이메일을 인증하기 위한 절차입니다." +
            "<br><br>" +
            "인증 번호는 " + authNumber + "입니다." +
            "<br>" +
            "회원 가입 폼에 해당 번호를 입력해주세요.";

        mailSend(email, title, content);

        // Redis에 인증 코드 저장
        UniversityVerification verification = UniversityVerification.builder()
            .email(email)
            .authCode(authNumber)
            .expiration(180L)
            .build();
        universityVerificationRepository.save(verification); // Redis에 저장
    }

    /* 재요청 메서드 */
    public void reRequest(String email) {
        universityVerificationRepository.deleteByEmail(email); // 기존 데이터 삭제
        sendVerificationEmail(email); // 동일한 메서드 호출로 재요청
    }

    /* 인증 코드 검증 메서드 */
    public void verifyAuthCode(String email, String code, Long id) {
        UniversityVerification verification = universityVerificationRepository.findByEmail(email)
            .orElseThrow(() -> new CustomException(MemberErrorCode.UNIVERSITY_VERIFICATION_NOT_FOUND));
            
        if (verification.getAuthCode().equals(code)) {
            Member member = memberRepository.findById(id)
                .orElseThrow(() -> new CustomException(MemberErrorCode.MEMBER_NOT_FOUND));
            member.setAuth(true);
            memberRepository.save(member);
        } else {
            throw new CustomException(MemberErrorCode.INVALID_AUTH_CODE);
        }
    }
}

 

 

📝 테스트 진행하기 

의존성 설정
@ExtendWith(MockitoExtension.class)
public class UniversityVerificationServiceTest {

    @Mock
    private JavaMailSender javaMailSender;

    @Mock
    private UniversityVerificationRepository universityVerificationRepository;

    @Mock
    private MemberRepository memberRepository;

    @InjectMocks
    private UniversityVerificationService universityVerificationService;

    private static final String EMAIL = "quddnddl35@naver.com";
    private static final Long MEMBER_ID = 1L;

 

해당 서비스는 인자로 javaMailSender, universityVerificationRepository, memberRepository를 가져오므로 3가지를 목으로 지정했습니다. 또한 로직에 필요한 EMAIL과 MEMBER_ID를 미리 지정했습니다.

 

이메일 전송 메소드 테스트
    /* 이메일 전송 */
    public void mailSend(String toMail, String title, String content) {
        MimeMessage message = javaMailSender.createMimeMessage();
        try {
            message.setFrom(serviceName); // 발신자 이메일 설정
            message.setRecipients(MimeMessage.RecipientType.TO, toMail); // 수신자 이메일 설정
            message.setSubject(title); // 이메일 제목 설정

            // 이메일 본문 설정 (HTML 형식)
            message.setText(content, "UTF-8", "html");

            // 이메일 전송
            javaMailSender.send(message);
        } catch (MessagingException e) {
            log.error("Failed to send email to: {}. Error: {}", toMail, e.getMessage());
            throw new CustomException(MailErrorCode.FAILED_MAIL_SEND);
        }
    }

    /* 이메일 작성 및 인증 코드 저장 */
    public void sendVerificationEmail(String email) {
        String authNumber = makeRandomNum(); // 난수 생성
        String title = "회원 가입을 위한 이메일입니다!";
        String content = "이메일을 인증하기 위한 절차입니다." +
            "<br><br>" +
            "인증 번호는 " + authNumber + "입니다." +
            "<br>" +
            "회원 가입 폼에 해당 번호를 입력해주세요.";

        mailSend(email, title, content);

        // Redis에 인증 코드 저장
        UniversityVerification verification = UniversityVerification.builder()
            .email(email)
            .authCode(authNumber)
            .expiration(180L)
            .build();
        universityVerificationRepository.save(verification); // Redis에 저장
    }

이메일 전송 메소드입니다. 해당 메소드를 검증하기 위해서는 2가지 Verif 작업이 필요합니다.

  • sendVerificationEmail - universityVerificationRepository.save : Redis 저장 메소드가 잘 호출되는지 검사
  • mailSend - javaMailSender.send : 메일이 전달되었는지 검사
    @Test
    public void testSendVerificationEmail() {
        // given
        MimeMessage message = Mockito.mock(MimeMessage.class); // MimeMessage Mock 생성
        when(javaMailSender.createMimeMessage()).thenReturn(message); // createMimeMessage 메서드 Mock 설정
        doNothing().when(javaMailSender).send(any(MimeMessage.class)); // 이메일 전송 메서드 Mock 설정

        // when
        universityVerificationService.sendVerificationEmail(EMAIL);

        // then
        verify(javaMailSender, times(1)).send(any(MimeMessage.class)); // 이메일 전송이 호출되었는지 검증
        verify(universityVerificationRepository, times(1)).save(any(UniversityVerification.class)); // Redis 저장이 호출되었는지 검증
    }

 

 

        // given
        MimeMessage message = Mockito.mock(MimeMessage.class); // MimeMessage Mock 생성
        when(javaMailSender.createMimeMessage()).thenReturn(message); // createMimeMessage 메서드 Mock 설정
        doNothing().when(javaMailSender).send(any(MimeMessage.class)); // 이메일 전송 메서드 Mock 설정

MinmeMesage 목을 생성하는 이유는 javaMailSender 메소드 자체가 패키지로 있기 때문에 해줘야 합니다.

 

universityVerificationService.sendVerificationEmail 호출 시에 동작하는 javaMailSender를 Mock으로 만듭니다.

그 이유는 실제로 동작하기 때문에 이것을 목으로 만들어 주는 것입니다. 그래서 실제 동작에서 실행되는 것은 

Redis 저장밖에 없습니다. 

 

이메일 검증 코드 테스트
    /* 인증 코드 검증 메서드 */
    public void verifyAuthCode(String email, String code, Long id) {
        UniversityVerification verification = universityVerificationRepository.findByEmail(email)
            .orElseThrow(() -> new CustomException(MemberErrorCode.UNIVERSITY_VERIFICATION_NOT_FOUND));

        if (verification.getAuthCode().equals(code)) {
            Member member = memberRepository.findById(id)
                .orElseThrow(() -> new CustomException(MemberErrorCode.MEMBER_NOT_FOUND));
            member.setAuth(true);
            memberRepository.save(member);
        } else {
            throw new CustomException(MemberErrorCode.INVALID_AUTH_CODE);
        }
    }

 

  • 이메일 검증 성공 시 
    @Test
    public void testVerifyAuthCode_Success() {
        // given
        String authCode = "123456";
        UniversityVerification verification = new UniversityVerification();
        verification.setEmail(EMAIL);
        verification.setAuthCode(authCode);

        when(universityVerificationRepository.findByEmail(EMAIL)).thenReturn(Optional.of(verification));
        Member member = new Member();
        member.setId(MEMBER_ID);
        when(memberRepository.findById(MEMBER_ID)).thenReturn(Optional.of(member));

        // when
        universityVerificationService.verifyAuthCode(EMAIL, authCode, MEMBER_ID);

        // then
        assertThat(member.isAuth()).isTrue(); // 회원 인증 상태 검증
        verify(memberRepository, times(1)).save(member); // 멤버 저장이 호출되었는지 검증
    }

한번 요령을 터득하면 보일 것입니다. 해당 메소드에서 조회 관련을 모두 임의의 가짜 데이터로 바꾸고 Member를 바꾸는 로직만 실행하는 것을 볼 수 있습니다. 

 

  • 이메일 검증 실패 시 
 @Test
    public void testVerifyAuthCode_Failure() {
        // given
        UniversityVerification verification = new UniversityVerification();
        verification.setEmail(EMAIL);
        verification.setAuthCode("wrongCode");
		
        // when
        when(universityVerificationRepository.findByEmail(EMAIL)).thenReturn(Optional.of(verification));

        // then
        assertThatThrownBy(() -> universityVerificationService.verifyAuthCode(EMAIL, "123456", MEMBER_ID))
            .isInstanceOf(CustomException.class);
    }

이메일 검증 실패 시에는 오류를  리턴하기에 오류가 발생 했는지 검사합니다.

 

 

 

✔참고 

Mockito framework site

[Spring] JUnit과 Mockito 기반의 Spring 단위 테스트 코드 작성법 (3/3) - MangKyu's Diary (tistory.com)

 

[Spring] JUnit과 Mockito 기반의 Spring 단위 테스트 코드 작성법 (3/3)

이번에는 Spring 기반의 웹 애플리케이션에서 테스트를 작성하는 방법에 대해 알아보도록 하겠습니다. 1. Mockito 소개 및 사용법 [ Mockito란? ] Mockito는 개발자가 동작을 직접 제어할 수 있는 가짜 객

mangkyu.tistory.com