Blog

Mit heißer Nadel: TDD, JEE und needle4j

Will man testgetrieben entwickeln (bzw. überhaupt Unit-testen) und gleichzeitig mit einem Dependency Injection Framework arbeiten, steht man zwangsläufig vor einem wesentlichen Problem beim Aufbau der Tests: Wie bekomme ich die, mitunter komplexen, Abhängigkeitsnetzwerke meiner Komponenten pro zu testender Unit aufgebaut oder besser gekapselt (bzw. gemockt)? Schließlich erfolgt die DI zur Laufzeit durch ein komplexes Framework, das ggf. aufwändig konfiguriert werden muss und wegen der Objekt-Beziehungen der Anwendung selten ohne Nebeneffekte ist, sprich sich für einzelne Klassen (die Unites-under-Test) nicht bzw. schwer isolieren lässt.

Üblicherweise ergeben sich hier folgende Möglichkeiten:

  1. Man verwendet einen Laufzeitcontainer für die Testausführung, der eine abgespeckte Variante der Produktiv-Laufzeitumgebung ist. Ein typischer Vertreter wäre ein Arquillian-Test. Problem: wenn sich die Zielumgebung nicht komplett „embedded“ nutzen lässt (wie z.B. beim JBoss-Container) muss unter Umständen ein aufwändiges Deployment zusammengestellt werden, bis die Tests überhaupt lauffähig sind.
    Da die Ausführung der Tests wirklich auf dem Server stattfindet, ist die Grenze zum Integrationstest im besten Falle fließend, aus grundsätzlicher Betrachtung schon überschritten (die Grenzregion zwischen Unit- und Integrationstest ist immer wieder ein Quell anregender Debatten. Dafür kann gerne die Kommentarfunktion genutzt werden).
    Ebenfalls problematisch: Die Verwendung von Mocks in Arquillian ist nicht praktikabel, es müssen also unter Umständen Fake-Instanzen gebaut werden, die ein bestimmtest Verhalten erzwingen.
  2. Will man den Laufzeitcontainer umgehen, bleibt die Möglichkeit, die Dependencies der Komponenten „per Hand“ im Test-Setup einzustellen. Geht man dabei nicht den Weg, die Kapselung des Produktivcodes aufzuweichen (public Setter für eigentlich nicht veränderliche Attribute, package-visible fields, Setter-Injection/Constructor-Injection statt Field-Injection, …) muss man in der @Before-Methode eine Menge Java-Reflection anwenden um erzeugte Mocks in die Objekte unter Test zu injecten.

Als Alternative kommt genau hier needle4j ins Spiel. needle4j ist ein „Dependency-Injection Simulator“, der pauschal für alle Injection-Points des Objektes unter Test entsprechende Mocks generiert und diese per Reflection injected. Dadurch schrumpft das Setup eines Tests auf eine Rule und zwei Annotationen zusammen.

Am einfachsten lässt sich das an einem kleinen Beispiel demonstrieren. Wir nehmen zwei Klassen Foo und Bar an. Dabei soll Foo per Injection von Bar abhängen:

public class Foo {
    @Inject
    private Bar bar;

    public String hello() { return bar.getText();}
}

public interface Bar {
    String getText();
}

Die konkrete Implementierung von Bar ist für den Test von Foo uninteressant, es reicht uns, wenn ein gemockter Wert zuverlässig zurückgegeben wird.
Der Test mit needle4j sieht wie folgt aus:

    // initialisiere needle für die Verwendung mit Mockito.
    @Rule
    public final NeedleRule needle = NeedleBuilders.needleMockitoRule().build();

    // needle erzeugt eine Instanz unserer CuT und kümmert sich um DI, dabei wird automatisch ein Mock von Bar erzeugt.
    @ObjectUnderTest
    private Foo foo;

    // Wir lassen uns die Instanz des Mocks injecten, um sein Verhalten zu modifizieren
    @Inject
    private Bar bar;

    @Test
    public void foo_returns_text_from_bar() {
        when(bar.getText()).thenReturn("hello from bar");
        assertThat(foo.hello()).isEqualTo("hello from bar");
    }

Dabei kennt needle4j keine Scopes o.ä. Das bedeutet, es treten keine Nebeneffekte auf, es muss kein weiteres Set-Up o.ä. vorgenommen werden. Needle erzeugt eine Instanz von Foo, erkennt die Dependency auf Bar, erzeugt einen Mockito Mock und setzt ihn ein. Diesen Mock holen wir uns im Test, um ihn zu konfigurieren. Fertig.

Mit diesem einfachen und immer gleichen Aufbau unterstützt uns needle beim Erzeugen gut geschnittener Unit-Tests. Wenn mehr Set-Up erforderlich wird als im Bespiel, befinden wir uns vermutlich außerhalb des Scopes einer „Unit“ und sollten uns konkret die Frage stellen: „Was soll getestet werden?“
Doch selbst, wenn das einfache Mock-Setup nicht mehr ausreichen sollte, lässt uns das Framework nicht im Stich: Mittels InjectionProviders lässt sich das DI-Verhalten kontrollieren, es lassen sich zum Beispiel reale Objekte statt Mocks verwenden oder im Rahmen von Integrationstests sogar Remote-Services konfigurieren usw.

Anzumerken ist noch, dass needle nicht auf die in JSR330 bzw. JSR299 festgelegten Annotationen limitiert ist. Per Konfiguration lässt sich jede beliebige Annotation als „DI-relevant“ markieren, was z.B. das Testen von Springs „@Autowired“ genauso erlaubt wie JEEs „@EJB“.

Im zweiten Teil dieses needle4j-Blogs wird ein weiteres Feature vorgestellt werden: Die DatabaseRule, mit der sich Entities und DAOs komfortabel testen lassen, ohne auf externe Datasources angewiesen zu sein.

Needle4j ist über Maven Central verfügbar und steht als Open Source unter LGPL auf github zur Verfügung.

Über den Autor

Jan Galinski

Jan Galinski ist Senior Consultant bei der Holisticon AG und seit vielen Jahren als Architekt und Entwickler in agilen Kundenprojekten unterwegs. Er ist ein leidenschaftlicher Prozessautomatisierer und BPM-Craftsman, am liebsten mit Camunda BPM. Als Contributor zu zahlreichen Open Source Projekten aus den Bereichen BPM und JEE gibt er seine Erfahrung und Wissen gerne weiter. 2016 wurde er mit dem Camunda Community Award ausgezeichnet.

Antwort hinterlassen