Tuesday, September 06, 2011

Unit testing with JavaMail

It can be inconvenient to unit test a class which uses Javamail (needs SMTP server, hard to verify email contents). It is possible however to replace the standard mail Transport with a mock implementation. First you need two files in META-INF which make sure the mock gets used; in a Maven project, src/test/resources would be a good place for these. The file javamail.providers gives the transport class to use for the smtp protocol:
# set the javamail default provider to a mock implementation for SMTP only.
#
protocol=smtp; type=transport; class=com.myCompany.MockTransport; vendor=Acme Corporation;
In addition, the file javamail.address.map maps address types to the transport to use, its contents are:
rfc822=smtp
Finally, you need a mock Transport subclass which overrides the connect() methods and adds an implementation for sendMessage(). This example stores the last message sent in a static member; this makes it possible to retrieve the message afterwards in a unit test by using MockTransport.getLastMessage().
package com.myCompany;
import javax.mail.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Mock implementation of {@link Transport} for unit testing. This needs property file overrides
 * in a META-INF on the classpath in order to work; this is in src/test/resources .
 * See http://sujitpal.blogspot.com/2006/12/mock-objects-for-javamail-unit-tests.html for more information.
 * This implementation stores the last message sent and adds a getter for retrieval and verification.
 * @author bartas
 */
public class MockTransport extends Transport {
	 
	private static final Logger logger = LoggerFactory.getLogger(MockTransport.class);
	
	private static Message lastMessage;
	
	public MockTransport(Session session, URLName urlName) {
		super(session, urlName);
		logger.warn("constructed MockTransport instance - javamail is mocked");
	}

	/**
	 * Stores the message to send in this instance.
	 * @see javax.mail.Transport#sendMessage(javax.mail.Message, javax.mail.Address[])
	 */
	@Override
	public void sendMessage(Message arg0, Address[] arg1)
			throws MessagingException {
		
		String subject = arg0.getSubject();
		StringBuffer addresses = new StringBuffer("[");
		for (int i=0; i<arg1.length; i++) {
			addresses.append(arg1[i]);
			if (i<arg1.length-1) addresses.append(",");
		}
		addresses.append("]");
		logger.debug("sendMessage(\"{}\",{})", subject, addresses.toString());
		lastMessage = arg0;
	}
	
	
	@Override
	public void connect() throws MessagingException {
		logger.debug("connect()");
	}

	@Override
	public void connect(String arg0, int arg1, String arg2, String arg3)
			throws MessagingException {
		logger.debug("connect({},{},{},{})", new Object[] {arg0,arg1,arg2,arg3});
	}

	@Override
	public void connect(String arg0, String arg1, String arg2)
			throws MessagingException {
		logger.debug("connect({},{},{})", new Object[] {arg0,arg1,arg2});
	}

	@Override
	public void connect(String arg0, String arg1) throws MessagingException {
		logger.debug("connect({},{})", arg0,arg1);
	}

	@Override
	public synchronized void close() throws MessagingException {
		logger.debug("close()");
	}

	public static Message getLastMessage() {
		return lastMessage;
	}

}