【问题标题】:How to mock a javax.mail.Session如何模拟 javax.mail.Session
【发布时间】:2012-01-03 01:10:44
【问题描述】:

我需要在我的单元测试中模拟一个 javax.mail.Session 对象。 类 javax.mail.Session 被标记为 final,因此 Mockito 无法创建模拟。有谁知道如何解决这个问题?

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

【问题讨论】:

    标签: java email mockito


    【解决方案1】:

    您可以稍微重构您的代码。在 Martin Fowler 的“Working with Legacy Code”一书中,他描述了一种将外部 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

    【讨论】:

    • 一点评论。您可以决定只“包装”那些您在应用程序中真正使用的 javax.mail.Session 方法。
    • MimeMessage 构造函数需要一个 Session 对象。您将如何处理?
    【解决方案2】:

    使用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 测试中使用它,所以我不能在我的 testCase 上放置第二个 TestRunner =)
    • 这是一个如何运行结合两个运行器的测试用例的想法eclipse.org/forums/index.php/m/708337
    • 对于那些使用 TestNG 而不是 @RunWith 的人,让您的测试扩展“PowerMockTestCase”
    【解决方案3】:

    您可以使用Mock JavaMail 项目。我首先从Kohsuke Kawaguchi 找到它。 Alan Franzoni 在他的博客上也有一个primer for it

    当你把这个 jar 文件放在你的类路径中时,它会将任何邮件发送到内存邮箱中,可以立即检查。它非常易于使用。

    将这个添加到你的类路径中无疑是一种相当笨拙的模拟方法,但无论如何你很少希望在你的自动化测试中发送真正的电子邮件。

    【讨论】:

    【解决方案4】:

    如果你想模拟最终类,你可以使用 JDave 取消终结器,可以在这里找到:http://jdave.org/documentation.html#mocking

    它使用CGLib在JVM加载时动态改变字节码,将类转换为非最终类。

    然后可以将此库与 JMock2 (http://www.jmock.org/mocking-classes.html) 一起使用来进行测试,因为据我所知,Mockito 与 JDave 不兼容。

    【讨论】:

    • 然后您可以自己创建一个 Mockery,添加您的所有期望并在您的测试方法结束时调用 assertIsSatisfied 以验证所有预期的调用是否已完成。有一个很好的例子:jmock.org/getting-started.html 唯一缺少的调用是 assertIsSatisfied,因为它是使用 RunWith(JMock) 自动调用的,但不是强制使用 JMock 的 RunWith,您可以在方法末尾设置 assertIsSatisfied然后你可以使用你的@RunWith(Arquillian.class)
    【解决方案5】:

    使用 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));
    };
    

    【讨论】:

      【解决方案6】:

      请参阅running under JUnit 3 的 PowerMock 文档,因为它没有运行器,或者使用字节码操作工具。

      【讨论】:

        【解决方案7】:

        如果您可以将 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));
        }
        

        【讨论】:

          【解决方案8】:

          这是一个很老的问题,但您始终可以使用JavaMail API 实现自己的传输。使用您自己的传输,您可以根据this documentation 进行配置。这种方法的一个好处是您可以对这些消息做任何您想做的事情。也许您会将它们存储在一个哈希/集合中,然后您可以确保它们实际上是在您的单元测试中发送的。这样,您不必模拟最终对象,您只需实现自己的对象。

          【讨论】:

            【解决方案9】:

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

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2016-10-26
              • 1970-01-01
              • 2011-01-08
              • 2014-11-26
              • 1970-01-01
              • 1970-01-01
              • 2012-08-03
              • 2014-07-01
              相关资源
              最近更新 更多