单元测试邮件发送方法时如何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,步骤如下:
- 添加
mockito-inline测试依赖(Maven示例):
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <version>4.11.0</version> <!-- 使用最新稳定版 --> <scope>test</scope> </dependency>
- 编写测试代码,用
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:
- 添加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>
- 编写测试类,通过注解指定要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()); // 其他参数验证逻辑... } }
更优方案:重构代码,消除静态依赖
静态方法测试复杂度高,建议重构代码,将邮件发送逻辑封装为可注入的依赖:
- 定义邮件发送接口:
public interface EmailSender { void send(MimeMessage message) throws MessagingException; }
- 实现默认发送器:
public class DefaultEmailSender implements EmailSender { @Override public void send(MimeMessage message) throws MessagingException { Transport.send(message); } }
- 修改原方法,替换静态调用为接口调用:
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); } }
- 编写单元测试,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.
相关产品推荐
相关产品推荐

