fredag 5 oktober 2012

Unit testing for classes using javax.mail.Transport

Found an interesting framework dumbster that simplifies unit testing of mail sending services and APIs. It basically creates a fake mail server that intercepts all SMTP commands.

Here is a simple example of a unit test for the class BasicMailHelper which has a method send that sends an email using SMTP.

private static final int PORT = 9966;
private static final String HOST = "localhost";
private static final String USER = "aUser";
private static final String FROM = "aFrom";

private static Session session;

@BeforeClass
public static void createSession()
{
   // create the mail session
   Properties props = new Properties();
   props.put("mail.smtp.host", HOST);
   props.put("mail.smtp.port", Integer.toString(PORT));
   props.put("mail.smtp.user", USER);
   props.put("mail.smtp.from", FROM);
   props.put("mail.smtp.auth", Boolean.toString(false));
   props.put("mail.transport.protocol", "smtp");
   session = Session.getInstance(props);
}

@Test
public void testSendTo()
     throws MessagingException, IOException
{
   // create the fake SMTP dumbster server
   SimpleSmtpServer server = SimpleSmtpServer.start(PORT);

   // send the email using the class that is to be tested
   MailHelper mailHelper = new BasicMailHelper.Builder().setSession(session).build();
   String subject = "aSubject";
   String body = "aBody";
   InternetAddress to = new InternetAddress("test@test.com");
   mailHelper.send(subject, body, to);

   // make sure all requests are processed by stopping the server
   server.stop();

   // evaluate the result by examining the emails in dumbster
   Assert.assertEquals(1, server.getReceivedEmailSize());
   SmtpMessage mail = (SmtpMessage) server.getReceivedEmail().next();
   Assert.assertEquals(subject, mail.getHeaderValue("Subject"));
   Assert.assertEquals(to.getAddress(), mail.getHeaderValues("To")[0]);
   Assert.assertTrue(mail.getBody().contains(body));
}

After some more testing of dumbster I needed to compensate for dead-lock (i.e. a timing related missing notify) and missing error handling. This is what I ended up with (would be nice to have an updated version of dumpster). I use a CountDownLatch to handle the dead-lock, reflection to get the state of the private field serverSocket to see if the server is correctly initialized (e.g. has not thrown port already in use).

private final static int PORT_MIN = 8000;
private final static int PORT_MAX = 9000;
private static final Logger LOGGER = LoggerFactory.getLogger(MailTest.class);

private void startDumbster()
{
   int port = PORT_MIN;
   final CountDownLatch[] startedLatch = {null};
   while (true)
   {
      if (port > PORT_MAX)
      {
         Assert.assertTrue("Unable to find free port", false);
      }
      // compensate for dead-lock in SimpleSmtpServer.start and missing status flags
      startedLatch[0] = new CountDownLatch(1);
      new Thread(new Runnable()
      {
         public void run()
         {
            server = SimpleSmtpServer.start(port); // might lock the thread, use a timeout
            startedLatch[0].countDown();
          }
      }).start();
      startedLatch[0].await(1, TimeUnit.MINUTES);
      Field socketField = SimpleSmtpServer.class.getDeclaredField("serverSocket");
      socketField.setAccessible(true);
      if ((startedLatch[0].getCount() == 0) && !server.isStopped() && (socketField.get(server) != null))
      {
         LOGGER.info("Successfully started dumpster on port {}", port);
         break;
      } else
      {
         port++;
      }
   }
}

Inga kommentarer:

Skicka en kommentar