Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I mock classes that are difficult to instantiate (javax.mail.Message)?

I want to use junit tests in my next project, but I am unsure which of the several mock packages I should use. I also read through a couple of tutorials but I did not find info how to solve the specific problem outlined below. Maybe the feature is not available in the packages that I have checked out.

Here's the problem: I want to write an email filter class which iterates over a List<javax.mail.Message> and filters the email messages by subject, date, from, to etc.The code to test looks like this:

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;
}

So the obvious test case is to construct a List with a few messages and check whether the new list returned by the filter contains the right messages.

But javax.mail.Message is abstract and can't be instantiated directly. I would need to set up a real email store to do that, including account name and password.

So my questions are:

  • How do I mock a few javax.mail.Message objects with different values so that my filter class can call message.getSentDate() and other Message methods, retrieving the values that I have defined in the test setup code?

  • Which of the mock packages are best suited for this kind of problem?

All answers are really appreciated.

like image 211
nn4l Avatar asked May 22 '11 11:05

nn4l


1 Answers

I have tried both approaches to learn about the advantages and disadvantages:

Approach 1: create a new MockMessage class that implements only those methods I need in my filter code, then write standard JUnit tests.

The problem with the missing abstract method is actually trivial, as those methods can be generated by the IDE I am using (they will be empty but they are not needed by the code under test anyway). No Mock package is actually used.

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());
    }
}

Approach 2: using jMock and the ClassImposteriser. The amount of code is a bit less than in approach 1 and no extra MockMessage class is required.

I still prefer the first approach as it is simpler to understand. The second approach may be useful in other situations where objects are much more difficult to instantiate than 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());
    }
}
like image 115
nn4l Avatar answered Nov 07 '22 03:11

nn4l