본문 바로가기

자바

[spring] 메일 송신 기능 테스트(GreenMail)

진행 중인 토이 프로젝트는 아이디 / 비밀번호 찾기 기능에서 메일 송신 기능을 이용한다. 스프링부트를 이용하는 경우  springboot-starter-mail 패키지를 통해 메일 송신 기능과 관련된 기능을 이용할 수 있다.

package com.blaxsior.board.domain.mail;

import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import java.util.Map;

@Service
@RequiredArgsConstructor
public class MailService {
    @Value("${spring.mail.username}")
    public void setFromEmail(String fromEmail) {
        this.fromEmail = fromEmail;
    }
    private String fromEmail;

    private final JavaMailSender mailSender;
    private final TemplateEngine templateEngine;
    public void sendMail(String toEmail, String subject, String body)  {
        MimeMessage message = mailSender.createMimeMessage();
        try {
            MimeMessageHelper helper = new MimeMessageHelper(message);
            helper.setFrom(fromEmail);
            helper.setTo(toEmail);
            helper.setSubject(subject);
            helper.setText(body, true);
        } catch (MessagingException e) {
            throw new MailSendFailException("메일 전송에 실패했습니다.");
        }

        mailSender.send(message);
    }

    public void sendMail(String toEmail, String subject, String template, Map<String, Object> variables) {
        Context context = new Context();
        context.setVariables(variables);

        String body = templateEngine.process(template, context);
        this.sendMail(toEmail, subject, body);
    }
}
  • JavaMailSender: 메일을 보내는데 사용되는 클래스
  • MimeMessage: 다양한 미디어 타입(이미지, 문서 등)을 저장할 수 있는 메일 타입

 위와 같은 메일 서비스를 테스트하려면 어떻게 해야 할까? 단순히 메일 객체를 Mock 객체로 만들어서 호출되었는지 검사하는 방법도 있겠지만, 이 방식은 실제 메일이 어떻게 작성되었는지 내용을 검사하기 힘들다는 단점이 있다. 또한 실제 메일 서버와 통신하는 것이 아니므로 테스트가 정확하게 성공했다고 보장하기 어렵다.

이런 상황에 사용할 만한 라이브러리가 있는데, 바로 GreenMail이다.

GreenMail

 GreenMail는 이메일 테스트를 위한 경량 메일 서버를 포함하고 있는 포함한 라이브러리이다. 메일 서버를 mocking하는 기능을 가지고 있어, 메일 기능을 테스트할 때 사용하기 적합하다.

사용법

첫번째로, greenmail 의존성을 추가한다.

testImplementation 'com.icegreen:greenmail-junit5:2.0.0'

 

이후 테스트 클래스에 GreenMailExtension을 추가한다.

@SpringBootTest(
        classes = BoardApplication.class,
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
@TestPropertySource(locations = {"classpath:/application-test.properties"})
public class MailServiceTest {

    @RegisterExtension
    static GreenMailExtension greenMail = new GreenMailExtension(ServerSetupTest.SMTP)
            .withConfiguration(GreenMailConfiguration.aConfig().withUser("springboot", "springboot"))
            .withPerMethodLifecycle(false);
}
  • @RegisterExtension: JUnit5에 확장 클래스를 추가하기 위한 어노테이션. 테스트 전후로 처리하고 싶은 작업을 클래스 형태로 지정하는 방식으로, 현재 어노테이션이 붙은 클래스의 before/after + Each/All 메서드를 적절한 시기에 호출하게 된다.
  • GreenMailExtension: GreenMail 객체에 대한 확장. 프록시 객체를 제공한다.

위와 같이 설정했다면, 테스트를 위한 준비는 사실상 끝났다고 보면 된다. 이후 내가 작성한 서비스 클래스를 테스트한다.

@Test
void sendEmail() throws MessagingException {
    var toEmail = "test1@test.com";
    var subject = "test subject";
    var body = "hello world";

    mailService.sendMail(toEmail, subject, body);

    var messages = greenMail.getReceivedMessages();
    System.out.println("length is " + messages.length);
    var message = messages[0];
    var new_body = GreenMailUtil.getBody(message);
    System.out.println(new_body);

    assertThat(new_body).contains(body);
    assertThat(message.getAllRecipients().length).isEqualTo(1);
    assertThat(message.getSubject()).isEqualTo(subject);
}

테스트 방식은 다음 흐름을 따른다.

  1. 테스트 할 메일 서비스의 메서드를 호출한다.
  2. 메일 서비스가 greenmail 메일 서버에 메일을 보낸다.
  3. greenmail.getReceivedMessages( ): 을 호출하여 수신된 메일 목록을 얻는다.
  4. GreenMailUtil을 활용하거나, 메일 객체 자체의 프로퍼티를 검사하며 테스트를 진행한다.

이때, greenmail은 각 테스트를 호출할 때마다 메일 박스를 초기화해주지 않는다. 테스트에서 보낸 메일은 그대로 greenMail 객체 안에 남아 있게 되므로, 연관 없는 메일이 각 테스트에 영향을 줄 수 있다.

만약 각 테스트를 시작할 때 메일 박스를 비우고 싶다면, purgeEmailFromAllMailBoxes 메서드를 호출한다.

    @BeforeEach
    public void beforeEach() throws FolderException {
        mailService = new MailService(mailSender, templateEngine);
        // from email 따로 설정해줘야 함.
        mailService.setFromEmail(username);
        greenMail.purgeEmailFromAllMailboxes();
    }

위 설정 덕분에 getReceivedMessages( )[0]이 현재 테스트에서 보낸 메일이 되어, 송신자나 제목 등을 기반으로 검색하지 않고도 쉽게 메일을 얻을 수 있었다.

'자바' 카테고리의 다른 글

[spring] @Sql 어노테이션  (0) 2024.08.08
[spring] thymleaf 배포 시 template might not be accessible 문제  (0) 2024.05.29
[spring] 외부 설정 사용  (0) 2024.05.24