AssertJ - Einführung: Unterschied zwischen den Versionen

Aus MimiPedia
K (Ullrich verschob die Seite AssertJ Einführung nach AssertJ - Einführung, ohne dabei eine Weiterleitung anzulegen)
 
(2 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 3: Zeile 3:
Die Struktur von AssertJ-Anweisungen könnte einfacher nicht sein.  
Die Struktur von AssertJ-Anweisungen könnte einfacher nicht sein.  
Jede Assertion in AssertJ folgt dem gleichen Aufbau; umgangsprachlich läßt sie so ausdrücken:
Jede Assertion in AssertJ folgt dem gleichen Aufbau; umgangsprachlich läßt sie so ausdrücken:
{{quote|assert that object X has property Y}}
{{quote|assert that X has property Y}}
oder auf Deutsch:
oder auf Deutsch:
{{quote|stelle sicher, daß das Objekt X die Eigenschaft Y besitzt}}
{{quote|stelle sicher, daß X die Eigenschaft Y besitzt}}
Sie besteht also aus zwei Teilen: dem zu prüfenden Objekt X und der Prüfung Y. Jede -- also zumindest fast jede -- Assertion beginnt mit dem Aufruf der statischen Methode:     
Sie besteht also aus zwei Teilen: dem zu prüfenden Objekt X und der Prüfung Y. Nahezu jede Assertion beginnt mit dem Aufruf der selben statischen Methode:     
{{java|code=Assertions.assertThat(...)}}
{{java|code=Assertions.assertThat(...)}}
Sie hat als einzigen Parameter einen Java-Ausdruck der das zu prüfende Objekt liefert. Der Ausdruck kann so ziemlich alles sein,
Das hat einen unschätzbaren Vorteil gegenüber JUnit-Assertion, denn man braucht sich für den Einstieg nur eine
typischerweise beschreibt er eine lokale Variable, ein Objekt, oder ein Methoden-Aufruf.
einzige Methode zu merken. Bei der Auswahl der Test-Methoden hilft die IDE via Code-Completion.
Auf dem damit erzeugten Objekt kann man -- je nach Typ -- eine Reihe von Methoden aufrufen, die die zu prüfende Eigenschaft darstellen.
 
Die assertThat-Methode hat als einzigen Parameter einen Java-Ausdruck der das zu prüfende Objekt liefert.
Der Ausdruck kann so ziemlich alles sein, typischerweise beschreibt er eine lokale Variable, ein Objekt, oder
einen Methoden-Aufruf. Auf dem dambei erzeugten Objekt kann man -- je nach Typ -- eine Reihe von Methoden aufrufen,
die die zu prüfende Eigenschaft darstellen.
 
Betrachten wir mal als Beispiel die Prüfung, ob {{java|2 + 2}} tatsächlich {{java|4}} ergibt. Dafür schreiben wir zunächst einmal
Betrachten wir mal als Beispiel die Prüfung, ob {{java|2 + 2}} tatsächlich {{java|4}} ergibt. Dafür schreiben wir zunächst einmal
{{java|code=
{{java|code=Assertions.asserThat(2 + 2)}}
Assertions.asserThat(2 + 2)
Tippen wir nun einen Punkt ein, bietet die IDE eine Fülle von Methoden an -- wir möchten auf Gleichheit
}}
Tippen wir nun einen Punkt ein, bietet Eclipse eine Fülle von Methoden an -- wir möchten auf Gleichheit
testen, wählen für unser Beispiel {{java|.isEqualTo()}} aus und schreiben
testen, wählen für unser Beispiel {{java|.isEqualTo()}} aus und schreiben
{{java|code=
{{java|code=Assertions.asserThat(2 + 2).isEqualTo(4);}}
Assertions.asserThat(2 + 2).isEqualTo(4);
Schon ist die Assertion fertig. Aus der Fülle der Prüf-Methdoen werden in den folgenden Abschnitten einige
}}
der meistverwendeten vorgestellt.
Schon ist die Assertion fertig. Abhängig vom Typ des zu prüfenden Ausdrucks im {{java|assertThat}}-Aufruf
 
stehen unterschiedliche Methoden zur Überprüfung zur Verfügung.
In den folgenden Abschnitten werden die meistverwendeten vorgestellt.
= Ausführung =
= Ausführung =
Geht die Assertion schief, bemüht sich AssertJ um eine möglichst hilfreiche Meldung:
Geht die Assertion schief, bemüht sich AssertJ um eine möglichst hilfreiche Meldung:
Zeile 32: Zeile 33:
  org.junit.ComparisonFailure: [2 + 2] expected:<[5]> but was:<[4]>
  org.junit.ComparisonFailure: [2 + 2] expected:<[5]> but was:<[4]>


= Objekte =
=Objekte=
 
==Gleichheit==
==Gleichheit==
Normalerweise vergleicht man Objekte auf inhaltliche Gelichheit über die {{java|.equals()}} Methode. Das geht in AssertJ mit:
Normalerweise vergleicht man Objekte auf inhaltliche Gelichheit über die {{java|.equals()}} Methode. Das geht in AssertJ mit:
{{java|code=assertThat(x).isEqualsTo(Y)}}
{{java|code=assertThat(x).isEqualTo(Y)}}
Vergleicht man enum-Ausprägungen oder möchte sicherstellen -- was einem Vergleich mit == entspricht -- verwendet man     
Vergleicht man enum-Ausprägungen oder möchte sicherstellen daß zwei Objekte identisch sind
-- was einem Vergleich mit == entspricht -- verwendet man     
{{java|code=assertThat(x).isSameAs(Y)}}
{{java|code=assertThat(x).isSameAs(Y)}}


Zeile 47: Zeile 48:
}}
}}
==Welcher Typ?==
==Welcher Typ?==
Mit
Um zu prüfen ob das Objekt Instanz einer Klasse ist oder ncht prüft man mit
{{java|code=assertThat(X).isInstanceOf(Foo.class);}}
{{java|code=
kann geprüft werden, ob das Objekt eine Instanz der angegebenen Klasse ist.
assertThat(X).isInstanceOf(Foo.class);
assertThat(X).isNotInstanceOf(Foo.class)
}}
Diese Bedinung ist auch dann erfüllt, wenn X einer Unterklasse von {{java|Foo}} angehört (oder nicht).
Möchte man sichergehen, daß exakt gegen {{java|Foo}} geprüft wird, verwendet man stattdessen
{{java|code=
assertThat(X).isExactlyInstanceOf(Foo.class);
assertThat(X).isNotExactlyInstanceOf(Foo.class)
}}
 
==Boolean und boolean==
==Boolean und boolean==
Für die Boolean-Typen stehen zwei Methoden mit offensichtlichem Verhalten zur Verfügung:
Für die Boolean-Typen stehen zwei Methoden mit offensichtlichem Verhalten zur Verfügung:
Zeile 74: Zeile 84:
während {{java|containsInstanceOf()}} den Typ von {{java|x.get()}} prüft.
während {{java|containsInstanceOf()}} den Typ von {{java|x.get()}} prüft.


== Listen, Arrays, Kollektionen ==
== <span id="matches"></span>Der Eierlegende Wollmilch-Test ==
Besonders hilfreich ist AssertJb ei der Prüfung von Listen aller Art.
Nahezu jede Test-Situation für die man nicht sofort eine passende Lösungin AssertJ findet,
Sehr angenehm ist dabei die gleichartige Behandlung von Typen wie Streams, Arrays und Collections wie List und Set
läßt sich mit einem {{lambda}}-Ausdruck bewältigen der en Test beschreibt. Nicht zuletzt,
die alle mit den gleichen Methoden getestet werden. Das bedeutet, daß weniger Methoden zu lernen sind und
weil man hier beliebige Funktionen aufrufen kann die jeden erdenklichen Test ausführen.
wenn sich der Typ eines Ausdrucks ändert - z.B. von {{java|List}} nach {{java|Stream}} - muß der Test nicht angepaßt werden.
 
Das Einfachste ist die Suche nach bestimmten Werten:
{{java|code=Assertions.assertThat(liste).contains("x", "y");}}
Die Prüfung gelingt, wenn die Liste -- gegebenenfalls neben anderen Strings -- die String "x" und "y"
an beliebiger Stelle enthält. Daneben gibt es eine ganze Legion von contains-Methoden wie z.B.
{{java|code=
containsAnyOf
containsExactly
containsExactlyInAnyOrder
containsNull
containsOnly
}}
Analog dazu gibt es negierte Methoden, die mit {{java|doesNotContain}} beginnen.
 
Bei simplen Typen wie {{java|String} oder Zahlen funktionieren die contains-Methoden sehr gut. Bei Objekten muß man in die Objekte
hineinschauen und wenn die {{java|.equals}}-Methode nicht passend implementiert wurde, erhält man unbefriedigende Ergebnisse.
 
Verwendet man λ-Prädikate kann man {{java|anyMatch}} und {{java|noneMatch}} verwenden.
Möchten wir etwa sicherstellen, daß die Liste ein Objekt mit der ID "5" enthält, könnte das so aussehen:
{{java|code=
Assertions.assertThat(liste).anyMatch(x -> x.id().equals(5));
}}
= Selbstgebaute Test-Bedingungen -- Conditions =
Grundsätzlich lassen sich alle Test-Ausdrücke mit den genannten Konstrukten formulieren. Strenggenommen kann man ja jeden Test als
boolean-Ausdruck formulieren und dann auf True oder False prüfen. Das führt jedoch zu unübersichtlichen Tests und zu endlosen
Code-Duplikationen. Wiederkehrende Prüfungen lassen sich mit Hilfe von wiederverwertbaren {{java|Conditions}} definieren.
Betrachten wir dazu als einfaches Beispiel (der Einfachheit halber ohne getter und setter) eine Person die einen Namen
hat und einer Abteilung zugeordnet ist:
{{java|code=
class Person {
    String name;
    String abteilung;
    Person(String name, String abteilung) {
        this.name = name;
        this.abteilung = abteilung;
    }
    @Override
    public String toString() {
        return name;
    }
}
}}
Um zu testen ob eine Person zur Abteilung "X" gehört kann ein Prädikat mit Hilfe der Methode {{java|matches()}} nutzen:
{{java|code=
Assertions.assertThat(person).matches(p -> p.abteilung.equals("X"));
Assertions.assertThat(person).matches(p -> p.abteilung.equals("X"), "ist in X");
}}
Funktional sind beide Varianten identisch, die Angabe der Beschreibung im zweiten Falle erleichtert aber die Interpretation
der Fehlermeldung. Um nicht jedesmal das Prädikat und die Beschreibung neu tippen zu müssen, kann man beides zu einer
{{java|Condition}} zusammenfassen und ''irgendwo'' ablegen:
{{java|code=
Condition<Person> InAbteilungX = new Condition<>(p -> p.abteilung.equals("X"), "ist in X");
}}
Der Aufruf sieht dann im Unit-Test so aus:
{{java|code=
Assertions.assertThat(person).is(InAbteilungX);
}}
Conditions lassen sich mit {{java|not()}} negieren, mit {{java|allOf()}} -- das entspricht einer und-Verknüpfung -- und mit
{{java|anyOf()}} -- das entspricht einer oder-Verknüpfung -- verbinden.


Für die Erzeugung von Conditions kann man auch Factory-Methoden definieren. Hier wird zu einer Abteilung eine Condition erzeugt,
Das ist hilfreich, wenn man feststeckt und weitermachen möchte. Man sollte jedoch erwägen
die die Zugehörigkeit einer Person zu einer Abteilung testet:
-- zu einem späteren Zeitpunkt -- nachzuforschen ob's nicht eleganter geht.
Die magische Methode heißt {{java|matches}} und nimmt ein Prädikat vom passenden Typ als Parameter:
{{java|code=
{{java|code=
static Condition<Person> inAbteilung(String abt) {
assertThat("foo").matches((String s) -> s.trim().length() == 3);
    Predicate<Person> pred = p -> p.abteilung.equals(abt);
    String description = "Abteilung " + abt;
    return new Condition<Person>(pred, description);
}
}}
}}
im Unit-Test sieht der Aufruf damit so aus:
Hier in einem anschaulichen, wenn auch einigermaßen sinnfreien Beispiel.
{{java|code=Assertions.assertThat(person).is(inAbteilung("X"));}}
Ist die Condition nicht erfüllt, erhält man folgende Ausgabe. Für die Ausgabe des Namens ist die {{java|toString}}-Methode
der Klasse {{java|Person}} verantwortlich:
java.lang.AssertionError:
Expecting:
  <Kasimir>
to have:
  <Abteilung X>


=Exceptions=
=Exceptions=
Zeile 207: Zeile 144:
}}
}}
= Mehrere Assertions in einem Test =
= Mehrere Assertions in einem Test =
Eine Grundregel für unit-Tests lautet: ''Eine'' Assertion je Test. Warum sollte man also mehrere Assertions in einem
Eine Grundregel für Unit-Tests lautet: ''Eine'' Assertion je Test. Warum sollte man also mehrere Assertions in einem
Test ausführen? Möchte man nach Ausführung eins Tests z.B. prüfen ob ein Adreß-Objekt korrekt befüllt ist, ist es in der Regel
Test ausführen? Möchte man nach Ausführung eins Tests z.B. prüfen ob ein Adreß-Objekt korrekt befüllt ist, ist es in
einfacher Alle Felder in einem Test abzufragen. Bisweilen hat man auch komplexere Situationen zu prüfen, etwa wenn ein
der Regel einfacher Alle Felder in einem Test abzufragen. Bisweilen hat man auch komplexere Situationen zu prüfen,
Objekt-Geflecht erzeugt wird und die Struktur geprüft wird. Dann möchte man nicht einfach die Meldung bekommen "geht nicht"
etwa wenn ein Objekt-Geflecht erzeugt wird und die Struktur geprüft wird. Dann möchte man nicht einfach die Meldung
sondern genauere Information darüber was nicht stimmt.
bekommen "geht nicht" sondern genauere Information darüber was nicht stimmt.


Das Problem bei der Test-Azsführung besteht nun darin, daß das Test-Framework -- in der Regeln JUnit -- nach dem Auftreten
Das Problem bei der Test-Ausführung besteht nun darin, daß das Test-Framework -- in der Regeln JUnit -- nach dem
des ersten Fehlers den Test abbricht. Man führt den Test dann wieder und wieder aus und fixt jedesmal ''einen'' Fehler.
Auftreten des ersten Fehlers den Test abbricht. Man führt den Test dann wieder und wieder aus und fixt jedesmal
''einen'' Fehler.


AssertJ bietet -- mit den Soft-Assertions -- die Möglichkeit, die Assertion-Ergebnisse zu sammeln und am Ende der Test-Ausführung gesammelt und komplett
AssertJ bietet -- mit den Soft-Assertions -- die Möglichkeit, die Assertion-Ergebnisse zu sammeln und am Ende der
auszuwerten. Es giebt zwei Möglichkeiten die Soft-Assertions anzuwenden zunächst die konventionelle Variante:
Test-Ausführung gesammelt und komplett auszuwerten. Es giebt zwei Möglichkeiten die Soft-Assertions anzuwenden.
Hier zunächst die konventionelle Variante:
{{java|code=
{{java|code=
     SoftAssertions bunch = new SoftAssertions();
     SoftAssertions bunch = new SoftAssertions();
Zeile 227: Zeile 166:
Die Assertions werden in einem {{java|SoftAssertions}-Objekt gesammelt und im Anschluß an die Ausführung
Die Assertions werden in einem {{java|SoftAssertions}-Objekt gesammelt und im Anschluß an die Ausführung
durch den Aufruf von {{java|assertAll()}} gesammelt ausgewertet. Der Knackpunkt bei dieser Variante ist,
durch den Aufruf von {{java|assertAll()}} gesammelt ausgewertet. Der Knackpunkt bei dieser Variante ist,
daß der Aufruf leicht vergessen werden kann. Dann wird im Unit -- unbemerkt -- nichts geprüft!
daß der Aufruf leicht vergessen werden kann. Dann wird im Unit-Test -- unbemerkt -- nichts geprüft!


Dieses Problem kann man mit der {{lambda}}-Variante umgehen:
Dieses Problem kann man mit der {{lambda}}-Variante umgehen:
Zeile 239: Zeile 178:
Anstelle der unmittelbaren Ausführung wird der Block nun in einen {{lambda}}-Ausdruck verpackt.
Anstelle der unmittelbaren Ausführung wird der Block nun in einen {{lambda}}-Ausdruck verpackt.
Das {{java|SoftAssertions}-Objekt wird von der statischen Methode {{java|assertSoftly}} erzeugt,
Das {{java|SoftAssertions}-Objekt wird von der statischen Methode {{java|assertSoftly}} erzeugt,
der {{lambda}}-Ausdruck wird ausgeführt und {{java|assertAll()}} wird am Ende ausgeführt.
mit dem {{lambda}}-Ausdruck befüllt und {{java|assertAll()}} wird am Ende automatisch ausgeführt.
Kein Schritt kan vergessen werden.


Der Block des {{lambda}}-Ausdrucks kann zudem alle beliebigen Java-Anweisungen enthalten,
Der Block des {{lambda}}-Ausdrucks kann zudem alle beliebigen Java-Anweisungen enthalten,
wie zusätzliche Berechnungen, lokale Variablen etcetera.
wie zusätzliche Berechnungen, lokale Variablen etcetera.

Aktuelle Version vom 17. Juli 2021, 14:07 Uhr

Die grundlegende Syntax

Die Struktur von AssertJ-Anweisungen könnte einfacher nicht sein. Jede Assertion in AssertJ folgt dem gleichen Aufbau; umgangsprachlich läßt sie so ausdrücken:

assert that X has property Y

oder auf Deutsch:

stelle sicher, daß X die Eigenschaft Y besitzt

Sie besteht also aus zwei Teilen: dem zu prüfenden Objekt X und der Prüfung Y. Nahezu jede Assertion beginnt mit dem Aufruf der selben statischen Methode:

Assertions.assertThat(...)

Das hat einen unschätzbaren Vorteil gegenüber JUnit-Assertion, denn man braucht sich für den Einstieg nur eine einzige Methode zu merken. Bei der Auswahl der Test-Methoden hilft die IDE via Code-Completion.

Die assertThat-Methode hat als einzigen Parameter einen Java-Ausdruck der das zu prüfende Objekt liefert. Der Ausdruck kann so ziemlich alles sein, typischerweise beschreibt er eine lokale Variable, ein Objekt, oder einen Methoden-Aufruf. Auf dem dambei erzeugten Objekt kann man -- je nach Typ -- eine Reihe von Methoden aufrufen, die die zu prüfende Eigenschaft darstellen.

Betrachten wir mal als Beispiel die Prüfung, ob 2 + 2 tatsächlich 4 ergibt. Dafür schreiben wir zunächst einmal

Assertions.asserThat(2 + 2)

Tippen wir nun einen Punkt ein, bietet die IDE eine Fülle von Methoden an -- wir möchten auf Gleichheit testen, wählen für unser Beispiel .isEqualTo() aus und schreiben

Assertions.asserThat(2 + 2).isEqualTo(4);

Schon ist die Assertion fertig. Aus der Fülle der Prüf-Methdoen werden in den folgenden Abschnitten einige der meistverwendeten vorgestellt.

Ausführung

Geht die Assertion schief, bemüht sich AssertJ um eine möglichst hilfreiche Meldung:

Assertions.assertThat(2+2).isEqualTo(5);
org.junit.ComparisonFailure: expected:<[5]> but was:<[4]>

Sollte die Meldung nicht ausreichen, kann man nach dem Aufruf von assertThat eine Erklärung einfügen indem man die .as()-Methode aufruft:

Assertions.assertThat(2 + 2).as("2 + 2").isEqualTo(5);	
org.junit.ComparisonFailure: [2 + 2] expected:<[5]> but was:<[4]>

Objekte

Gleichheit

Normalerweise vergleicht man Objekte auf inhaltliche Gelichheit über die .equals() Methode. Das geht in AssertJ mit:

assertThat(x).isEqualTo(Y)

Vergleicht man enum-Ausprägungen oder möchte sicherstellen daß zwei Objekte identisch sind -- was einem Vergleich mit == entspricht -- verwendet man

assertThat(x).isSameAs(Y)

Null oder nicht Null

Dafür gibt es zwei Methoden mit naheliegendem Namen:

assertThat(x).isNull()
assertThat(x).isNotNull()

Welcher Typ?

Um zu prüfen ob das Objekt Instanz einer Klasse ist oder ncht prüft man mit

assertThat(X).isInstanceOf(Foo.class);
assertThat(X).isNotInstanceOf(Foo.class)

Diese Bedinung ist auch dann erfüllt, wenn X einer Unterklasse von Foo angehört (oder nicht). Möchte man sichergehen, daß exakt gegen Foo geprüft wird, verwendet man stattdessen

assertThat(X).isExactlyInstanceOf(Foo.class);
assertThat(X).isNotExactlyInstanceOf(Foo.class)

Boolean und boolean

Für die Boolean-Typen stehen zwei Methoden mit offensichtlichem Verhalten zur Verfügung:

Assertions.assertThat(x).isTrue()
Assertions.assertThat(x).isFalse()

Die Methoden funktionieren für beide Boolean-Typen, für Boolean ohne die Gefahr einer Null-Pointer-Exception.

Optional

Bevor man den Inhalt eines Optional-Objekts prüfen kann, muß man ihn erst auspacken; ein Job den AssertJ für uns übernimmt und dabei auch darauf achtet, ob statt eines validen Objekts vielleicht null übergeben wurde:

Assertions.assertThat(x).contains("foo")

Ist der Inhalt egal, kann man auch direkt die Präsenz oder Abwesenheit eines Wertes abfragen:

Assertions.assertThat(x).isPresent()  
Assertions.assertThat(x).isNotPresent()

Statt .isNotPresent() kann auch .empty() verwendet werden. Interessiert nur der Typ des enthaltenen Objekts, hilft AssertJ uns auch:

Assertions.assertThat(x).containsInstanceOf(String.class)

Man beachte den Unterschied zu .instanceOf(), das den Typ von x prüft, während containsInstanceOf() den Typ von x.get() prüft.

Der Eierlegende Wollmilch-Test

Nahezu jede Test-Situation für die man nicht sofort eine passende Lösungin AssertJ findet, läßt sich mit einem λ-Ausdruck bewältigen der en Test beschreibt. Nicht zuletzt, weil man hier beliebige Funktionen aufrufen kann die jeden erdenklichen Test ausführen.

Das ist hilfreich, wenn man feststeckt und weitermachen möchte. Man sollte jedoch erwägen -- zu einem späteren Zeitpunkt -- nachzuforschen ob's nicht eleganter geht. Die magische Methode heißt matches und nimmt ein Prädikat vom passenden Typ als Parameter:

assertThat("foo").matches((String s) -> s.trim().length() == 3);

Hier in einem anschaulichen, wenn auch einigermaßen sinnfreien Beispiel.

Exceptions

Mit der assertThat-Methode kann nicht getestet werden, ob bei einem Methoden-Aufruf eine Exception geworfen wird oder nicht. Das funktioniert deshalb nicht, weil der Ausdruck, der assetThat als Argument übergeben wird ausgewertet wird bevor die assertThat-Methode ausgeführt wird.

Für den Test auf Exceptions wird die Ausführung in einen λ-Ausdrck verpackt, dieser einer geeigneten Methode übergeben und as Ergebnis bei dessen Ausführung analysiert -- wie funktioniert das konkret?

Bei der ersten Variante wird ein λ-Ausdruck übergeben und anschließend die erwartete Exception angegeben. Im Beispiel wird die Methode foo des Objekts subject aufgerufen:

Assertions.assertThatThrownBy(() -> subject.foo()).isInstanceOf(NullPointerException.class);

Die erwartete Exception-Klasse wird von der Methode isInstanceOf() geprüft.

Die zweite Variante legt erst die erwartete Exeption fest und prüft dann den λ-Ausdruck:

Assertions.assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> subjekt.foo());

Die zweite Variante verstößt gegen das sonst in AssertJ durchgängige Konzept erst den Test-Aufruf und dann das erwartete Ergebnis anzugeben. Es gibt jedoch einige Methode für oft benötigte Exceptions:

Assertions.assertThatIllegalStateException().isThrownBy(() -> subjekt.foo());
Assertions.assertThatIllegalArgumentException().isThrownBy(() -> subjekt.foo());
Assertions.assertThatNullPointerException().isThrownBy(() -> subjekt.foo());
Assertions.assertThatIOException().isThrownBy(() -> subjekt.foo());

Geht das auch ohne λ?

Grundsätzlich sind λ-Ausdrücke und anonyme Klassen austauschbare Konzepte. Man kann daher λ-Ausdrücke durch Klassen ersetzen die das geeignete (funktionale) Interface implementieren. Die Methode assertThatThrownBy() akzeptiert als Parameter ein Objekt das das Interface ThrowingCallable implementiert. Daher kann man die erste Variante mit einer anonymen Klasse so schreiben:

Assertions.assertThatThrownBy(new ThrowingCallable() {
     @Override
     public void call() throws Throwable {
         subjekt.foo();
     }
 }).isInstanceOf(NullPointerException.class);

Die zweite Variante sieht dann -- wir verwenden hier die Spezial-Methode für Nullpointer-Exceptions -- so aus:

Assertions.assertThatNullPointerException().isThrownBy(new ThrowingCallable() {
     @Override
     public void call() throws Throwable {
         subjekt.foo();
     }
 });

Mehrere Assertions in einem Test

Eine Grundregel für Unit-Tests lautet: Eine Assertion je Test. Warum sollte man also mehrere Assertions in einem Test ausführen? Möchte man nach Ausführung eins Tests z.B. prüfen ob ein Adreß-Objekt korrekt befüllt ist, ist es in der Regel einfacher Alle Felder in einem Test abzufragen. Bisweilen hat man auch komplexere Situationen zu prüfen, etwa wenn ein Objekt-Geflecht erzeugt wird und die Struktur geprüft wird. Dann möchte man nicht einfach die Meldung bekommen "geht nicht" sondern genauere Information darüber was nicht stimmt.

Das Problem bei der Test-Ausführung besteht nun darin, daß das Test-Framework -- in der Regeln JUnit -- nach dem Auftreten des ersten Fehlers den Test abbricht. Man führt den Test dann wieder und wieder aus und fixt jedesmal einen Fehler.

AssertJ bietet -- mit den Soft-Assertions -- die Möglichkeit, die Assertion-Ergebnisse zu sammeln und am Ende der Test-Ausführung gesammelt und komplett auszuwerten. Es giebt zwei Möglichkeiten die Soft-Assertions anzuwenden. Hier zunächst die konventionelle Variante:

SoftAssertions bunch = new SoftAssertions();
    bunch.assertThat(adresse.getStrasse()).isEqulaTo("Alexanderplatz");
    bunch.assertThat(adresse.getHausnummer()).isEqulaTo(15);
    bunch.assertThat(adresse.getPostfach()).isNull();
    bunch.assertAll();

Die Assertions werden in einem {{java|SoftAssertions}-Objekt gesammelt und im Anschluß an die Ausführung durch den Aufruf von assertAll() gesammelt ausgewertet. Der Knackpunkt bei dieser Variante ist, daß der Aufruf leicht vergessen werden kann. Dann wird im Unit-Test -- unbemerkt -- nichts geprüft!

Dieses Problem kann man mit der λ-Variante umgehen:

SoftAssertions.assertSoftly(bunch -> {
    bunch.assertThat(adresse.getStrasse()).isEqulaTo("Alexanderplatz");
    bunch.assertThat(adresse.getHausnummer()).isEqulaTo(15);
    bunch.assertThat(adresse.getPostfach()).isNull();
));

Anstelle der unmittelbaren Ausführung wird der Block nun in einen λ-Ausdruck verpackt. Das {{java|SoftAssertions}-Objekt wird von der statischen Methode assertSoftly erzeugt, mit dem λ-Ausdruck befüllt und assertAll() wird am Ende automatisch ausgeführt.

Der Block des λ-Ausdrucks kann zudem alle beliebigen Java-Anweisungen enthalten, wie zusätzliche Berechnungen, lokale Variablen etcetera.