如何模拟难以实例化的类(javax.mail.Message)?

时间:2011-05-22 11:50:09

标签: java unit-testing mocking javamail

我想在我的下一个项目中使用junit测试,但我不确定应该使用哪几个模拟软件包。我还阅读了几个教程,但我没有找到如何解决下面列出的具体问题的信息。也许这个功能在我签出的软件包中没有。

问题在于:我想编写一个电子邮件过滤器类,它迭代List<javax.mail.Message>并按主题,日期,从等过滤电子邮件。要测试的代码如下所示:

public List<Message> doFilter(List<Message> messageList) {

    List<Message> newList = new ArrayList<Message>(messageList.size());

    try {
        for (Message message: messageList) {
            if (start != null) {
                Date sentDate = message.getSentDate();
                if (sentDate == null || sentDate.before(start))
                    continue;
            }
            if (end != null) {
                Date receivedDate = message.getReceivedDate();
                if (receivedDate == null || receivedDate.after(end))
                    continue;
            }
            newList.add(message);
        }
    }
    catch(Exception e) {
        e.printStackTrace();
    }
    return newList;
}

所以明显的测试用例是用一些消息构造一个List,并检查过滤器返回的新列表是否包含正确的消息。

但是javax.mail.Message是抽象的,无法直接实例化。我需要建立一个真正的电子邮件商店,包括帐户名和密码。

所以我的问题是:

  • 如何使用不同的值模拟几个javax.mail.Message对象,以便我的过滤器类可以调用message.getSentDate()和其他Message方法,检索我在中定义的值测试设置代码?

  • 哪种模拟软件包最适合此类问题?

非常感谢所有答案。

3 个答案:

答案 0 :(得分:2)

我尝试了两种方法来了解优点和缺点:

方法1:创建一个新的MockMessage类,它只在我的过滤器代码中实现我需要的那些方法,然后编写标准的JUnit测试。

缺少抽象方法的问题实际上是微不足道的,因为这些方法可以由我正在使用的IDE生成(它们将是空的,但无论如何它们都不是被测试的代码所需要的)。实际上没有使用Mock包。

public class MockMessage extends Message {

    private Date sentDate;

    public MockMessage(Date sentDate) {
        this.sentDate = sentDate;
    }

    public Date getSentDate() throws MessagingException {
        return sentDate;
    }

    // the rest of the required methods can easily be generated by the IDE
    ...
}


public void testFilter1() {

    try {
        DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyy-MM-dd hh:mm:ss");
        final Date date0 = dtf.parseDateTime("2011-05-19 05:51:26").toDate();
        final Date date1 = dtf.parseDateTime("2011-05-19 05:51:27").toDate();
        final Date date2 = dtf.parseDateTime("2011-05-19 05:51:28").toDate();

        final List<Message> mockMessages = Arrays.asList(
            (Message)new MockMessage(date0),
            (Message)new MockMessage(date1),
            (Message)new MockMessage(date2)
        );

        MessageFilter filter = new ByDate(date1, null);
        List<Message> result = filter.doFilter(mockMessages);
        assertEquals(result.size(),2);
        assertEquals(result.get(0).getSentDate(),date1);
        assertEquals(result.get(1).getSentDate(),date2);
    }
    catch(Exception e) {
        fail(e.getMessage());
    }
}

方法2:使用jMock和ClassImposteriser。代码量比方法1少一点,并且不需要额外的MockMessage类。

我仍然更喜欢第一种方法,因为它更容易理解。第二种方法在其他对象比javax.mail.Message更难实例化的情况下可能很有用。

public void testFilter2() {

    try {
        DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyy-MM-dd hh:mm:ss");
        final Date date0 = dtf.parseDateTime("2011-05-19 05:51:26").toDate();
        final Date date1 = dtf.parseDateTime("2011-05-19 05:51:27").toDate();
        final Date date2 = dtf.parseDateTime("2011-05-19 05:51:28").toDate();

        Mockery mockery = new Mockery();
        mockery.setImposteriser(ClassImposteriser.INSTANCE);

        final List<Message> mockMessages = Arrays.asList(
            mockery.mock(Message.class, "message 1"),
            mockery.mock(Message.class, "message 2"),
            mockery.mock(Message.class, "message 3")
        );

        mockery.checking(new Expectations() {{
           allowing(mockMessages.get(0)).getSentDate(); will(returnValue(date0));
           allowing(mockMessages.get(1)).getSentDate(); will(returnValue(date1));
           allowing(mockMessages.get(2)).getSentDate(); will(returnValue(date2));
        }});

        MessageFilter filter = new ByDate(date1, null);
        List<Message> result = filter.doFilter(mockMessages);
        assertEquals(result.size(),2);
        assertEquals(result.get(0).getSentDate(),date1);
        assertEquals(result.get(1).getSentDate(),date2);
    }
    catch(Exception e) {
        fail(e.getMessage());
    }
}

答案 1 :(得分:0)

只需创建一个扩展Message的MockMessage类。然后,您可以实现或覆盖您想要模拟的行为。

至于模拟框架,我发现这些问题比它们的价值更多,但YMMV。

答案 2 :(得分:0)

大多数模拟框架都应该没有问题,尽管你是在嘲笑类而不是界面这一事实会让它稍微尴尬。

这是一个使用JMock(我最熟悉的框架)的简单示例:

Mockery mockery = new Mockery();
mockery.setImposteriser(ClassImposteriser.INSTANCE);

final List<Message> mockMessages = asList(
    mockery.mock(Message.class, "message1"),
    mockery.mock(Message.class, "message2")
);

mockery.checking(new Expectations() {{
   allowing(mockMessages.get(0)).getSubject(); will(returnValue("some subject"));
   allowing(mockMessages.get(1)).getSubject(); will(returnValue("some other subject"));
}});

通常情况下,我会阻止人们嘲笑这类事情,只是直接创建对象,但JavaMail API使用起来很笨拙,不想这样做是可以理解的。