Das Versenden einer E-Mail ist ein wichtiger Bestandteil vieler Geschäftsprozesse. Leider ist es gar nicht so einfach, einen Service, der eine E-Mail verschickt, durch einen Unit-Test zu überprüfen. Mock-Objekte helfen zwar weiter, lassen aber offen, ob eine E-Mail wirklich versendet werden kann. Was kann man sonst tun?
Die meines Wissens beste Alternative ist, E-Mails von der Anwendung versenden zu lassen und im Unit-Test zu prüfen, ob eine E-Mail erfolgreich zugestellt worden ist. Dazu eignen sich hervorragend Fake-SMTP-Server.
Ein solcher Server ist nichts weiter als eine schlanke Bibliothek, die auf einem konfigurierbaren lokalen Port E-Mails empfängt. Zu Beginn eines Unit-Tests startet man den SMTP-Server, prüft nach dem Eintreffen der erwarteten Nachricht den Inhalt der E-Mail und fährt den Server am Ende des Unit-Tests wieder herunter. Je nach Umfang und Konfigurationsaufwand der verwendeten Bibliothek sind dazu nur wenige Zeilen Java-Code notwendig.
Hier ein Beispiel auf der Grundlage der Open-Source-Bibliothek Dumbster:
SimpleSmtpServer server = SimpleSmtpServer.start(); // Call service method int numerOfReceivedEMails = server.getReceivedEmailSize(); SmtpMessage message = (SmtpMessage) server.getReceivedEMail().next(); String body = message.getBody(); server.stop();
Wenn man nicht nur sicherstellen will, dass eine E-Mail eingetroffen ist, sondern auch den Inhalt der E-Mail überprüfen möchte, steht man vor dem Problem, die kodierte Nachricht zu dekodieren. Eine narrensichere Lösung dazu kenne ich leider nicht. Mein derzeit bester Ansatz ist der folgende Code:
InputStream inputStream = new ReaderInputStream(new StringReader(message.getBody())); String bodyText = IOUtils.toString(MimeUtility.decode(inputStream, "quoted-printable"));
Beim Hochfahren des SMTP-Servers trifft man schnell auf ein Problem: Der Standard-SMTP-Port (25) ist auf vielen Maschinen schon belegt bzw. lässt sich durch einen Java-Prozess ohne Root-Rechte nicht binden. Also muss man den Fake-SMTP-Server auf einem anderen Port starten und die Anwendung muss E-Mails entsprechend an diesen Port schicken. Wie stellt man aber sicher, dass ein gewählter Port immer frei ist?
Diese Frage kann glücklicherweise die Java-Laufzeitumgebung beantworten. Wenn man einen Server-Socket ohne Port-Vorgabe erzeugt, sucht die Laufzeitumgebung nach einem freien Port und bindet ihn. Wenn man nun den Server-Socket wieder schließt, kann man den zuvor gebundenen Port anschließend wiederverwenden. Hier ein Beispiel zu dieser Technik:
ServerSocket tmpSocket = new ServerSocket(0); int freePort = tmpSocket.getLocalPort(); tmpSocket.close(); SimpleSmtpServer server = SimpleSmtpServer.start(freePort);
Zugegeben: Absolut sicher ist diese Methode der Port-Auswahl nicht, aber man kommt damit schon sehr weit.
Der Einsatz eines Fake-SMTP-Servers bietet somit eine gute Grundlage, um Tests zu schreiben, die auch den Inhalt von versendeten E-Mails einbeziehen. Indem man einen Fake-SMTP-Server als Teil eines einzelnen Unit-Tests hochfährt, benötigt man keine zusätzliche zentrale Infrastruktur und kann somit in sich geschlossene Tests schreiben.