Verwendung eines SMTP-Servers in Unit-Tests

Im Rahmen von Unit-Tests möchte man die Anbindung externer Systeme vermeiden um Stabilität der Tests zu gewährleisten. Leider hat man genau dann Probleme, wenn man gerade diese Anbindung oder die Integration mit externen Systemen testen möchte.

Für solche Unit-Test mit integrativem Charakter kann man Mocks verwenden, die recht spezifisch für den jeweiligen Anwendungsfall selbst implementiert werden. Wenn es allerdings an die Implementierung eines Mail-Server Mocks geht, ist der Aufwand der Eigenimplementierung oft nicht mehr gerechtfertigt.

Verwendung eines Fake SMTP-Servers

Mit dem Dumbster Fake SMTP Server gibt es schon eine fertige Lösung. Dumbster kann zur Laufzeit ohne umfangreiche Konfiguration gestartet werden und verhält sich wie ein echter SMTP Server:

SimpleSmtpServer.start()

Per Default lauscht der SMTP-Server auf Port 25, was auf manchen Systemen entweder nicht erlaubt ist, oder auf Systemen mit eigenem Mailserver nicht funktioniert, weil der Port bereits vergeben ist. Speziell auf Unix-Systemen ist es empfehlenswert einen Port über 1024 zu wählen, damit man keine root-Rechte benötigt. Der gewünschte Port wird einfach in der start()-Methode übergeben. Um einen freien Port zu finden, bietet sich die Implementierung einer Hilfsmethode an, die statisch aufrufbar ist und mit JDK-Mitteln einen unbenutzten Port liefert:

class PortFinder {
  public static int getAvailablePort() {
    try {
      ServerSocket socket = new ServerSocket(0);
      int unusedPort = socket.getLocalPort();
      socket.close();
      return unusedPort;
    }
    catch (IOException e) {
      throw new RuntimeException("getAvailablePort", e);
    }
  }
}

Der Start des SMTP-Servers sieht nun so aus:

  int smtpPort = PortFinder.getAvailablePort();
  SimpleSmtpServer simpleSmtpServer = SimpleSmtpServer.start(smtpPort);

Ein typischer Unit-Test würde nun so konfiguriert werden, dass Mails an localhost und den oben ermittelten smtpPort gesendet werden.

Einbindung in Spring Integration

Für die Anbindung eines externen Mail-Servers bietet sich Spring Integration mit seinen Mail Channel Adaptern an. Eine Konfiguration im Produktivcode könnte mit Hilfe des Namespace Supports den Mailserver wie folgt definieren:

...
  <si-mail:outbound-channel-adapter channel="outboundMail" mail-sender="mailSender"/>
  <bean id="mailSender">
    <property name="javaMailProperties" ref="javaMailProperties"/>
    <property name="host" value="smtp.example.com"/>
  </bean>
...

Wird der Test als AbstractTestNGSpringContextTests implementiert, kann die MailSender-Bean auf einfache Weise überschrieben werden. Der SimpleSmtpServer wird ebenfalls als Bean zur Verfügung gestellt. Relevant ist hier das Attribut primary="true" an der Bean mit der überschriebenen id="mailSender":

  <bean id="smtpPort" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="staticMethod" value="de.hypoport.blog.PortFinder.getAvailablePort"/>
  </bean>

  <bean id="simpleSmtpServer" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="staticMethod" value="com.dumbster.smtp.SimpleSmtpServer.start"/>
    <property name="arguments" ref="smtpPort"/>
  </bean>

  <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl" primary="true">
    <property name="javaMailProperties" ref="javaMailProperties"/>
    <property name="host" value="localhost"/>
    <property name="port" ref="smtpPort"/>
  </bean>

Der Test kann nun wie folgt implementiert werden, er bindet sowohl die produktive Spring-Konfiguration als auch die oben aufgeführte Test-Konfiguration ein:

@Test
@ContextConfiguration({"classpath:spring-config/mailsender.spring.xml",
"classpath:spring-config/mailsender-test.spring.xml"})
public class SendMailTest extends AbstractTestNGSpringContextTests {

  @Autowired
  private SimpleSmtpServer simpleSmtpServer;
  @Resource(name = "inputChannel")
  private MessageChannel inputChannel;

  @Test
  public void testSend() throws Exception {
    String expectedSubject = "Mail Subject";
    Message<MimeMessage> mimeMessage = MessageBuilder.withPayload(createMimeMessage()).build();

    inputChannel.send(mimeMessage);
    simpleSmtpServer.stop();

    assertThat(simpleSmtpServer.getReceivedEmailSize()).isEqualTo(1);
    SmtpMessage email = (SmtpMessage) simpleSmtpServer.getReceivedEmail().next();
    assertThat(email.getHeaderValue("Subject")).isEqualTo(expectedSubject);
  }

  private MimeMessage createMimeMessage() {
    // das Erzeugen der MimeMessage ist hier nicht relevant
    return null;
  }
}

Dumbster bietet mehr als die hier gezeigten Möglichkeiten, die Menge der empfangenen Mails oder auch deren Inhalte zu verifizieren. Einige Beispiele findet man in den Beispielen der Dumbster Dokumentation.

Berlin Expert Days mit 4 Vorträgen unserer Mitarbeiter

Die Berlin Expert Days 2012 haben ein sehr interessantes Programm. Neben bekannten Speakern erhalten auch weniger bekannte Speaker die Gelegenheit sich und Ihre Erfahrungen zu präsentieren. U.a. gibt es 4 Vorträge von Hypoport Mitarbeitern:

Verena Würfel, eine freie Mitarbeiterin, präsentiert sich mit Was Frankenstein und Anwendungen gemeinsam haben.

@Mock unused variable mit IntelliJ IDEA kein Problem

Ich bin froh, dass IntelliJ IDEA die Standard IDE bei uns ist. Hier ein kleines Beispiel wie flexibel – manche nennen es intelligent – IntelliJ ist:)

Die Inspection unused variable warnt vor unbenutzten Variablen. Nach CleanCode lösche ich solche Variablen sehr gerne sehr schnell. Problematisch wird dies wenn man z.B. @Mock von Mockito oder @Resource von Spring verwendet. Was diese Annotationen inhaltlich machen gibt es ein anderes Mal. Für jetzt ist interessant, dass eine mit @Mock annotierte Variable nicht weiter im Code verwendet wird. Je nach gewählten Farbschema sieht das dann so aus:

Weiterlesen

Eigener ESB, andere ESBs oder einfach Spring Integration?

Bei Hypoport haben wir seit Jahren einen selbst entwickelten ESB im Einsatz. In unserem Team haben wir wenig KnowHow diesen zu nutzen und anzupassen. Die Anbindung und Erweiterung von externen Schnittstellen fällt daher nicht leicht. Erschwerend kommt hinzu, dass die Integration Flows nicht durch unser Continuous Delivery Skript automatisiert ausgerollt werden können. Wir standen vor der Entscheidung uns intensiv in diesen ESB einzuarbeiten oder es mit einer anderen Integrationslösung zu versuchen. Wir entschieden uns für letzteres. Es gibt mittlerweile eine Fülle an leistungsstarken Lösungen, die aktiv weiter entwickelt werden, große Communities und gute Dokumentation besitzen. Die Auswahl fiel schnell auf Spring Integration. In unserer Anwendung verwenden wir bereits diverse Spring Module und sind damit vertraut The Spring Way zu entwickeln. Die Zeit fehlte um eine ESB Evaluierung und eine Abstimmung mit anderen Teams durch zu führen. Unser Team hatte nur vereinzeltes und eher oberflächliches ESB KnowHow. Die Notwendigkeit eines eigenen Application-Server mit seinen eigenen Techniken und Technologien für Erzeugung und Deployment von Software-Artefakten hätte unsere derzeitige Systemlandschaft unnötig komplex macht.

Weiterlesen

Lessons learned – Der DelegatingFilterProxy von Spring

Wenn man einen ServletFilter für die eigene Webapp baut, dann muss man drei Methoden des Interfaces implementieren: init(), doFilter() und destroy(). Der Filter wird danach in die web.xml aufgenommen und startet zusammen mit der Webapp, wobei beim Starten des Filters seine init()-Methode aufgerufen wird.

So weit die Theorie. Spring ermöglicht es, Filter im ApplicationContext zu definieren und ihnen damit Zugriff auf andere Beans zu bieten. Über die Namenskonvention Filtername == BeanId wird nun nicht mehr die Filterklasse in der web.xml eingetragen, sondern eine Klasse aus Spring:

<filter>
  <filter-name>beispielFilter</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
  <filter-name>beispielFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

Weiterlesen