メール送信はまだまだ業務で重要ですね。javaでメール送信をする処理をユニットテストしたいけどメールが大量に飛んでしまうのは困るし、誤って本番環境のメールアドレスにメールが飛んだら大変です。そんな時はwiserを使いましょう!
SubEthaMail
GitHub - voodoodyne/subethasmtp: SubEtha SMTP is a Java library for receiving SMTP mail
wiserができる事は以下の通りです。
- jarのみで仮想SMTPを立てられる組み込みメールサーバ機能を持つ。
- アプリケーションからwiserに投げられたメールは破棄される。
- wiserは送信されたメールの内容を記憶しており、メールの宛先等が取得できる。
組み込みの仮想SMTPなので例えば以下が可能になります。
- JUnit開始。
- wiserを起動して仮想SMTPを起動。
- ユニットテストに書かれたメール送信が実行される。
- wiserはlistenしているportでメールを受信するが、メールは送信しない。
- ユニットテストが終了する。
- wiserが停止し、ポートのlistenも停止。
例えばSaStrutsでメール送信されるようなActionをテストする場合、JUnit起動と同時にwiserを開始し、JUnit終了時にwiserを停止すると、テスト自体は成功するがメールは送信されません。メールは送信されませんが送信された情報(subjectやmailto等)を詳細に保持しているので、メールの宛先や件名が想定通りなのか、をテストする事が可能になります。
では今回はjavamailを使ったユニットテストを紹介します。
build.grade
テストし易いようにgradleを使います。
apply plugin: 'java' apply plugin: 'eclipse' sourceCompatibility = 1.8 repositories { mavenCentral() } configurations { all*.exclude module: 'commons-logging' all*.exclude module: 'log4j' all*.exclude module: 'slf4j-jdk12' all*.exclude module: 'slf4j-jdk14' } dependencies { compile group: 'com.google.guava', name: 'guava', version: '18.0' compile group: 'commons-collections', name: 'commons-collections', version: '3.2' compile group: 'javax.mail', name: 'mail', version: '1.4.1' compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.10' compile group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.10' compile group: 'org.slf4j', name: 'jul-to-slf4j', version: '1.7.10' compile group: 'org.slf4j', name: 'log4j-over-slf4j', version: '1.7.10' compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.2' testCompile group: 'junit', name: 'junit', version: '4.11' testCompile group: 'org.subethamail', name: 'subethasmtp', version: '3.1.7' }
メール送信ユニットテスト
import java.util.Date; import java.util.List; import java.util.Properties; import javax.mail.Address; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.subethamail.wiser.Wiser; import org.subethamail.wiser.WiserMessage; public class VirtualSmtpTest { public static final Logger LOGGER = LoggerFactory .getLogger(VirtualSmtpTest.class); private Wiser wiser; @Before public void before() { wiser = new Wiser(); wiser.setPort(2500); wiser.setHostname("dummy.mail.com"); wiser.start(); } @After public void after() { wiser.stop(); } @Test public void() throws MessagingException { try { // メール送信 Properties prop = new Properties(); prop.put("mail.smtp.host", "dummy.mail.com"); prop.put("mail.smtp.port", "2500"); Session session = Session.getDefaultInstance(prop); MimeMessage mime = new MimeMessage(session); // from mime.addFrom(InternetAddress.parse("from@mail.com")); // to Address[] tos = { new InternetAddress("to1@mail.com"), new InternetAddress("to2@mail.com") }; mime.setRecipients(Message.RecipientType.TO, tos); // cc Address[] ccs = { new InternetAddress("cc1@mail.com"), new InternetAddress("cc2@mail.com") }; mime.setRecipients(Message.RecipientType.CC, ccs); // bcc Address[] bcc = { new InternetAddress("bcc1@mail.com"), new InternetAddress("bcc2@mail.com") }; mime.setRecipients(Message.RecipientType.BCC, bcc); mime.setSubject("タイトルです", "iso-2022-jp"); mime.setText("ほげほげ本文"); mime.setSentDate(new Date()); Transport.send(mime); // wiserが受信したデータをログ出力 List<WiserMessage> messages = wiser.getMessages(); for (WiserMessage wiserMessage : messages) { LOGGER.info("Receiver={}", wiserMessage.getEnvelopeReceiver()); LOGGER.info("Sender={}", wiserMessage.getEnvelopeSender()); } } catch (Exception ex) { ex.printStackTrace(); } } }
@beforeでwiserのインスタンス生成とwiserの開始、@afterでstop、たったそれだけです。
「dummy.mail.com」という有り得ないホスト名、port=2500という通常使わないポート番号にしておけば、誤って正しいメールサーバに向けてメールを送信しようとする自己も防ぎやすくなります。更に、メール送信処理の前にホスト名がdummy.mail.comか、portが2500か、もチェックすると、更に事故が減らせるかと思います。
これを実行した時には以下のログが出力されます。
2015/01/10 01:15:36:835 INFO - SMTPServer.start SMTP server *:2500 starting 2015/01/10 01:15:36:840 INFO - ServerThread.run SMTP server *:2500 started 2015/01/10 01:15:36:912 INFO - VirtualSmtpTest Receiver=to1@mail.com 2015/01/10 01:15:36:912 INFO - VirtualSmtpTest Sender=from@mail.com 2015/01/10 01:15:36:912 INFO - VirtualSmtpTest Receiver=to2@mail.com 2015/01/10 01:15:36:912 INFO - VirtualSmtpTest Sender=from@mail.com 2015/01/10 01:15:36:912 INFO - VirtualSmtpTest Receiver=cc1@mail.com 2015/01/10 01:15:36:913 INFO - VirtualSmtpTest Sender=from@mail.com 2015/01/10 01:15:36:913 INFO - VirtualSmtpTest Receiver=cc2@mail.com 2015/01/10 01:15:36:913 INFO - VirtualSmtpTest Sender=from@mail.com 2015/01/10 01:15:36:913 INFO - VirtualSmtpTest Receiver=bcc1@mail.com 2015/01/10 01:15:36:913 INFO - VirtualSmtpTest Sender=from@mail.com 2015/01/10 01:15:36:913 INFO - VirtualSmtpTest Receiver=bcc2@mail.com 2015/01/10 01:15:36:913 INFO - VirtualSmtpTest Sender=from@mail.com 2015/01/10 01:15:36:914 INFO - SMTPServer.stop SMTP server *:2500 stopping 2015/01/10 01:15:36:914 INFO - ServerThread.run SMTP server *:2500 stopped
WiserMessageからMimeMessageも取得できたり、rawヘッダー等も取得できたりします。
雑感
正直今までメール送信は恐怖で一杯でした。
常に「バグって本番環境のメアドに大量にメール送信したらどうしよう・・・」とか思ってたのですが、例えばpostfixやsendmail等のメールサーバーが全く入っていないEC2インスタンスを作り、そこでユニットテストすればたとえバグって本番環境のメアドでメール送信処理を実行しても安心です。
ログすら残さないので、メールアドレスという個人情報がメールサーバのログに残ってそれが漏洩してしまった!なんて事にもなりません。
ユニットテストだけでなく、ローカル環境に閉じた開発をしたい場合も、わざわざメールサーバを用意する必要が無くなるので、ちょっと送信テストしたい場合に大変有効に活用できると思います。
wiserはメール送信処理自体しないため実行速度が高速なので、ガシガシメール送信しちゃいましょう!!