如何模拟一个javax.mail.Session

11

我需要在单元测试中模拟一个javax.mail.Session对象。但是javax.mail.Session类是final的,因此Mockito无法创建模拟对象。有没有人有修复此问题的想法?

编辑: 我的测试是一个Arquillian测试,并且已经有一个注解@RunWith(Arquillian.class)。因此,powermock不是一个选择。

9个回答

10
您可以对代码进行一些重构。在马丁·福勒(Martin Fowler)的《处理旧代码》一书中,他描述了一种将外部 API(例如 Java Mail API)与您自己的应用程序代码分离的技术。该技术称为“包装和处理”,并且非常简单。
您只需要:
  1. 通过从 javax.mail.Session 类中提取方法来创建一个接口 MySession (这是您自己的内容)。
  2. 通过创建一个具体类(看起来有点像原始的 javax.mail.Session)来实现该接口。
  3. 在每个方法中,将调用委托给等效的 javax.mail.Session 方法。
  4. 创建一个实现 MySession 的模拟类 :-)
  5. 更新您的生产代码以使用 MySession 而不是 javax.mail.Session。
祝您测试愉快!
编辑:还请查看此博客文章:http://www.mhaller.de/archives/18-How-to-mock-a-thirdparty-final-class.html

1
一个小建议。您可以决定仅“包装”您的应用程序中实际使用的javax.mail.Session方法。 - nowaq
2
MimeMessage 构造函数需要一个 Session 对象。你会如何处理? - Baz

7

使用PowerMockito进行模拟。

@RunWith(PowerMockRunner.class)
// We prepare PartialMockClass for test because it's final or we need to mock private or static methods
@PrepareForTest(javax.mail.Session.class)
public class YourTestCase {

  @Test
  public void test() throws Exception {

    PowerMockito.doReturn(value).when(classUnderTest, "methodToMock", "parameter1");
  }
}

由于我想在Arquillian测试中使用它,所以我不能在我的测试用例上放置第二个TestRunner =) - martin
以下是如何运行组合两个测试器的测试用例的想法:http://www.eclipse.org/forums/index.php/m/708337/ - Boris Pavlović
1
对于使用TestNG的人们,可以让你的测试类继承"PowerMockTestCase",而不是使用注释"@RunWith"。 - RockMeetHardplace

5
您可以使用Mock JavaMail项目。我最初是从Kohsuke Kawaguchi那里发现的,Alan Franzoni在他的博客上也有详细介绍
将此jar文件放入类路径中后,任何发送邮件都会被替换为可立即检查的内存邮箱。使用起来非常简单。
将其添加到类路径可能是模拟某些内容的一种相对笨重的方式,但在自动化测试中通常不需要发送真实的电子邮件。

1
该链接出现了证书错误。这是一个已经纠正的版本。 http://blogs.java.net/blog/kohsuke/archive/2007/04/introducing_moc.html - Raystorm
谢谢,@Raystorm。然而你的链接有同样的问题。我已经用指向实际Java.net项目的链接替换了我的指向Kohsuke Kawaguchi过时博客文章的链接,并提供了另一个使用它的入门指南的链接。 - jhericks
好的,直接链接到“安全”版本怎么样。https://blogs.java.net/blog/kohsuke/archive/2007/04/introducing_moc.html - Raystorm

1

你可以创建一个Mockery,添加所有的期望,并在测试方法的结尾调用assertIsSatisfied来验证是否已经完成了所有的期望调用。这里有一个很好的例子:http://www.jmock.org/getting-started.html唯一缺少的调用是assertIsSatisfied,因为它会自动与RunWith(JMock)一起调用,但使用JMock的RunWith并不是必须的,你可以在方法的结尾处直接调用assertIsSatisfied,然后使用你的@RunWith(Arquillian.class)。 - DarkRift

1
使用Java 8函数!
public class SendEmailGood {
    private final Supplier<Message> messageSupplier;
    private final Consumer<Message> messageSender;

    public SendEmailGood(Supplier<Message> messageSupplier,
                         Consumer<Message> messageSender) {
        this.messageSupplier = messageSupplier;
        this.messageSender = messageSender;
    }

    public void send(String[] addresses, String from, 
                     String subject, String body) 
                     throws MessagingException {
        Message message = messageSupplier.get();
        for (String address : addresses) {
           message.addRecipient
             (Message.RecipientType.TO, new InternetAddress(address));
        }
        message.addFrom(new InternetAddress[]{new InternetAddress(from)});
        message.setSubject(subject);
        message.setText(body);
        messageSender.accept(message);
    } 
}

那么你的测试代码将会类似于下面的示例:

@Test
public void sendBasicEmail() throws MessagingException {
    final boolean[] messageCalled = {false};

    Consumer<Message> consumer = message -> {
            messageCalled[0] = true;
    };

    Message message = mock(Message.class);
    Supplier<Message> supplier = () -> message;

    SendEmailGood sendEmailGood = new SendEmailGood(supplier, consumer);
    String[] addresses = new String[2];

    addresses[0] = "foo@foo.com";
    addresses[1] = "boo@boo.com";
    String from = "baz@baz.com";
    String subject = "Test Email";
    String body = "This is a sample email from us!";

    sendEmailGood.send(addresses, from, subject, body);
    verify(message).addRecipient(Message.RecipientType.TO, new InternetAddress("foo@foo.com"));
    verify(message).addRecipient(Message.RecipientType.TO, new InternetAddress("boo@boo.com"));
    verify(message).addFrom(new InternetAddress[]{new InternetAddress("baz@baz.com")});
    verify(message).setSubject(subject);
    verify(message).setText(body);

    assertThat(messageCalled[0]).isTrue();
}

为了创建一个集成测试,需要插入真实的Session和Transport插件。
Consumer<Message> consumer = message -> {
    try {
        Transport.send(message);
    } catch (MessagingException e) {
        e.printStackTrace();
    }
};

Supplier<Message> supplier = () -> {
    Properties properties = new Properties();
    return new MimeMessage(Session.getDefaultInstance(properties));
};

0

我使用mock-javamail库。它只是替换了类路径中原有的javamail实现。你可以正常地发送邮件,但它只会发送到内存MailBox,而不是真正的邮箱。

最后,你可以使用MailBox对象来断言任何你想要检查的内容。


0

这是一个相当老的问题,但您始终可以使用 JavaMail API来实现自己的传输。通过自己的传输,您可以根据此文档进行配置。采用这种方法的好处之一是,您可以随意处理这些消息。也许您会将它们存储在哈希/集合中,然后可以确保它们在单元测试中实际发送。这样,您就不必模拟最终对象,您只需自己实现即可。


0

请查看PowerMock文档在JUnit 3下运行,因为它没有运行器,或使用一个字节码操作工具。


0
如果您可以将Spring引入到您的项目中,那么您就可以使用JavaMailSender并进行模拟。我不知道您的需求有多复杂。
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;

@Test
public void createAndSendBookChangesMail() {

    // Your custom service layer object to test

    MailServiceImpl service = new MailServiceImpl();

    // MOCK BEHAVIOUR

    JavaMailSender mailSender = mock(JavaMailSender.class);
    service.setMailSender(mailSender);

    // PERFORM TEST

    service.createAndSendMyMail("some mail message content");

    // ASSERT

    verify(mailSender).send(any(SimpleMailMessage.class));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接