martin
martin

Reputation: 980

How to mock a javax.mail.Session

i need to mock a javax.mail.Session object in my unit tests. The class javax.mail.Session is marked final so Mockito is not able to create a mock. Does anyone have an idea how to fix this?

Edit: My test is an Arquillian test and has already an annotation @RunWith(Arquillian.class). Therefore powermock is not an option.

Upvotes: 11

Views: 20622

Answers (9)

Daniel Hinojosa
Daniel Hinojosa

Reputation: 992

Use Java 8 Functions!

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

Then your test code will look something like the following:

@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] = "[email protected]";
    addresses[1] = "[email protected]";
    String from = "[email protected]";
    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("[email protected]"));
    verify(message).addRecipient(Message.RecipientType.TO, new InternetAddress("[email protected]"));
    verify(message).addFrom(new InternetAddress[]{new InternetAddress("[email protected]")});
    verify(message).setSubject(subject);
    verify(message).setText(body);

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

To create an integration test, plugin the real Session, and 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));
};

Upvotes: 1

jhericks
jhericks

Reputation: 5971

You can use the Mock JavaMail project. I first found it from Kohsuke Kawaguchi. Alan Franzoni also has a primer for it on his blog.

When you put this jar file in your classpath, it substitutes any sending of mail to in memory mailboxes that can be checked immediately. It's super easy to use.

Adding this to your classpath is admittedly a pretty heavy handed way to mock something, but you rarely want to send real emails in your automated tests anyway.

Upvotes: 5

qrtt1
qrtt1

Reputation: 7957

I use the mock-javamail library. It just replace the origin javamail implementation in the classpath. You can send mails normally, it just sends to in-memory MailBox, not a real mailbox.

Finally, you can use the MailBox object to assert anything you want to check.

Upvotes: 0

Lucas
Lucas

Reputation: 14959

This is a pretty old question, but you could always implement your own Transport using the JavaMail API. With your own transport, you could just configure it according to this documentation. One benefit of this approach is you could do anything you want with these messages. Perhaps you would store them in a hash/set that you could then ensure they actually got sent in your unit tests. This way, you don't have to mock the final object, you just implement your own.

Upvotes: 0

Brad
Brad

Reputation: 15879

If you can introduce Spring into your project you can use the JavaMailSender and mock that. I don't know how complicated your requirements are.

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

Upvotes: 0

nowaq
nowaq

Reputation: 2748

You may refactor your code a little bit. In the "Working with Legacy Code" book by Martin Fowler, he describes a technique of separating the external API (think Java Mail API) from your own application code. The technique is called "Wrap and Skin" and is pretty simple.

What you should do is simply:

  1. Create an interface MySession (which is your own stuff) by extracting methods from the javax.mail.Session class.
  2. Implement that interface by creating a concrete class (looking a bit like the original javax.mail.Session)
  3. In each method DELEGATE the call to equivalent javax.mail.Session method
  4. Create your mock class which implements MySession :-)
  5. Update your production code to use MySession instead of javax.mail.Session

Happy testing!

EDIT: Also take a look at this blog post: http://www.mhaller.de/archives/18-How-to-mock-a-thirdparty-final-class.html

Upvotes: 10

Dave Newton
Dave Newton

Reputation: 160211

See the PowerMock docs for running under JUnit 3, since it did not have runners, or use a byte-code manipulation tool.

Upvotes: 0

DarkRift
DarkRift

Reputation: 234

If you want to mock a final classes you can use the JDave unfinalizer which can be found here : http://jdave.org/documentation.html#mocking

It uses CGLib to alter the bytecode dynamically when the JVM is loaded to transform the class as a non final class.

This library can then be used with JMock2 ( http://www.jmock.org/mocking-classes.html ) to make your tests because as far as I know, Mockito is not compatible with JDave.

Upvotes: 1

Boris Pavlović
Boris Pavlović

Reputation: 64632

Use PowerMockito to mock it.

@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");
  }
}

Upvotes: 7

Related Questions