You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单元测试邮件发送方法时如何Stub jakarta.mail.Transport.send静态方法

邮件发送方法单元测试:拦截静态Transport.send()的方案

问题

我正在测试如下包含jakarta.mail.Transport.send(msg)调用的邮件发送方法,该方法会实际发送邮件:

public static void sendEmail(String emailSmtpHost, Integer emailSmtpPort, String emailAddressFrom, String emailAddressTo, String emailSubject, String emailBody) {
    
    try {
        Properties props = System.getProperties();
        props.put(Constants.MAIL_SMTP_HOST_PROPERTY_NAME, emailSmtpHost);
        props.put(Constants.MAIL_SMTP_PORT_PROPERTY_NAME, emailSmtpPort);
        Session session = Session.getDefaultInstance(props);
        MimeMessage msg = new MimeMessage(session);
        msg.addHeader("Content-type", "text/HTML; charset=UTF-8");
        msg.addHeader("Content-Transfer-Encoding", "8bit");
        msg.setFrom(new InternetAddress(emailAddressFrom));
        msg.setReplyTo(InternetAddress.parse(emailAddressFrom, false));
        msg.setSubject(emailSubject, "UTF-8");
        msg.setText(emailBody, "UTF-8");
        msg.setSentDate(new Date());
        msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(emailAddressTo, false));
        Transport.send(msg);

    }
    catch (Exception e) {
        log.error("Error when sending email: ", e);     
    }

在单元测试该方法时,我需要验证Message的各项参数是否正确设置,但希望避免执行Transport.send(msg)的实际调用。尝试以下代码未成功:

Transport transportSpy = Mockito.spy(new Transport());
    doNothing().when(transportSpy).send(Mockito.any()); 

由于无法实例化Transport,我需要实现对Transport.send()静态方法的拦截,请问该如何操作?

解决方案

方法1:使用Mockito静态Mock(3.4.0+版本支持)

Mockito从3.4.0版本开始支持静态方法Mock,步骤如下:

  1. 添加mockito-inline测试依赖(Maven示例):
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>4.11.0</version> <!-- 使用最新稳定版 -->
    <scope>test</scope>
</dependency>
  1. 编写测试代码,用try-with-resources创建静态Mock,拦截并验证:
@Test
void testSendEmail() {
    // 静态Mock Transport类,拦截send方法
    try (MockedStatic<Transport> mockedTransport = Mockito.mockStatic(Transport.class)) {
        mockedTransport.when(() -> Transport.send(Mockito.any(MimeMessage.class)))
                      .thenAnswer(invocation -> {
                          // 获取传入的MimeMessage并验证参数
                          MimeMessage msg = invocation.getArgument(0);
                          assertEquals("test@from.com", msg.getFrom()[0].toString());
                          assertEquals("test@to.com", msg.getRecipients(Message.RecipientType.TO)[0].toString());
                          assertEquals("Test Subject", msg.getSubject());
                          return null;
                      });

        // 调用待测试方法
        EmailUtils.sendEmail("smtp.host", 587, "test@from.com", "test@to.com", "Test Subject", "Test Body");

        // 验证send方法被调用一次
        mockedTransport.verify(() -> Transport.send(Mockito.any(MimeMessage.class)), Mockito.times(1));
    }
}

方法2:使用PowerMock(兼容旧版Mockito)

若项目使用旧版Mockito,可通过PowerMock实现静态方法Mock:

  1. 添加PowerMock依赖(Maven示例):
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>2.0.9</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito2</artifactId>
    <version>2.0.9</version>
    <scope>test</scope>
</dependency>
  1. 编写测试类,通过注解指定要Mock的静态类:
@RunWith(PowerMockRunner.class)
@PrepareForTest(Transport.class)
public class EmailUtilsTest {

    @Test
    void testSendEmail() throws Exception {
        // Mock Transport的静态send方法
        PowerMockito.mockStatic(Transport.class);
        PowerMockito.doNothing().when(Transport.class, "send", Mockito.any(MimeMessage.class));

        // 调用待测试方法
        EmailUtils.sendEmail("smtp.host", 587, "test@from.com", "test@to.com", "Test Subject", "Test Body");

        // 捕获传入的MimeMessage并验证参数
        ArgumentCaptor<MimeMessage> messageCaptor = ArgumentCaptor.forClass(MimeMessage.class);
        PowerMockito.verifyStatic(Transport.class, Mockito.times(1));
        Transport.send(messageCaptor.capture());

        MimeMessage capturedMsg = messageCaptor.getValue();
        assertEquals("test@from.com", capturedMsg.getFrom()[0].toString());
        // 其他参数验证逻辑...
    }
}

更优方案:重构代码,消除静态依赖

静态方法测试复杂度高,建议重构代码,将邮件发送逻辑封装为可注入的依赖:

  1. 定义邮件发送接口:
public interface EmailSender {
    void send(MimeMessage message) throws MessagingException;
}
  1. 实现默认发送器:
public class DefaultEmailSender implements EmailSender {
    @Override
    public void send(MimeMessage message) throws MessagingException {
        Transport.send(message);
    }
}
  1. 修改原方法,替换静态调用为接口调用:
public static void sendEmail(String emailSmtpHost, Integer emailSmtpPort, String emailAddressFrom, String emailAddressTo, String emailSubject, String emailBody, EmailSender emailSender) {
    
    try {
        // 原有MimeMessage创建逻辑不变
        emailSender.send(msg); // 替换静态调用

    } catch (Exception e) {
        log.error("Error when sending email: ", e);     
    }
}
  1. 编写单元测试,Mock接口即可:
@Test
void testSendEmail() throws MessagingException {
    // Mock EmailSender接口
    EmailSender mockSender = Mockito.mock(EmailSender.class);
    doNothing().when(mockSender).send(Mockito.any(MimeMessage.class));

    // 调用测试方法并传入Mock实例
    EmailUtils.sendEmail("smtp.host", 587, "test@from.com", "test@to.com", "Test Subject", "Test Body", mockSender);

    // 捕获MimeMessage验证参数
    ArgumentCaptor<MimeMessage> captor = ArgumentCaptor.forClass(MimeMessage.class);
    verify(mockSender, times(1)).send(captor.capture());
    MimeMessage msg = captor.getValue();
    // 参数验证逻辑...
}

这种方式不仅测试更简单,代码扩展性也更强,后续更换邮件实现只需新增EmailSender实现类即可。

内容的提问来源于stack exchange,提问作者gene b.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 11:02:55