Du schreibst einen Test. Der Test schlägt fehl. Du starrst auf die Fehlermeldung – und verstehst erst nach zehn Sekunden, was da eigentlich schiefgelaufen ist.
expected:<[foo]> but was:<[bar]>
Danke, JUnit. Sehr aufschlussreich.
Das ist kein Randproblem. Unleserliche Fehlermeldungen kosten echte Zeit, besonders in größeren Codebasen, wo ein roter Test nicht immer offensichtlich ist. Google Truth ist eine Assertion-Library, die genau das löst – mit einer Fluent API, die sowohl im Code als auch in der Fehlermeldung klar lesbar bleibt.
Was ist Google Truth?
Google Truth ist eine Open-Source-Assertion-Library für Java, entwickelt von Google. Sie ist kein Test-Framework – kein Ersatz für JUnit oder TestNG – sondern ein Ersatz für assertEquals, assertTrue und Konsorten.
Das Ziel ist simpel: Assertions sollen sich wie natürliche Sprache lesen, und Fehlermeldungen sollen dir sofort sagen, was falsch ist – ohne Interpretation.
Truth ist seit Jahren in Googles internem Codebase im Einsatz und wird aktiv weiterentwickelt. Für Java-Projekte lässt sie sich in Minuten einbinden.
Setup
Maven:
<dependency>
<groupId>com.google.truth</groupId>
<artifactId>truth</artifactId>
<version>1.4.4</version>
<scope>test</scope>
</dependency>
Gradle:
testImplementation 'com.google.truth:truth:1.4.4'
Den Import brauchst du im Test:
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
Kein weiteres Setup. Truth funktioniert out-of-the-box mit JUnit 4, JUnit 5 und TestNG.
Das Kernkonzept: Subjects
Der Einstiegspunkt ist immer assertThat(). Was danach kommt, hängt vom Typ deines Objekts ab – Truth wählt automatisch den passenden Subject aus.
Ein Subject ist eine typsichere Wrapper-Klasse, die genau die Assertions anbietet, die für diesen Typ sinnvoll sind. StringSubject hat andere Methoden als IterableSubject oder MapSubject.
// String
assertThat("hello world").contains("world");
assertThat("hello world").startsWith("hello");
assertThat("hello world").doesNotContain("foo");
// Integer
assertThat(42).isGreaterThan(10);
assertThat(42).isAtMost(100);
assertThat(42).isEqualTo(42);
// Boolean
assertThat(isActive).isTrue();
assertThat(hasErrors).isFalse();
Das liest sich fast wie ein Satz. Und genau so liest sich auch die Fehlermeldung, wenn etwas schlägt.
Fehlermeldungen: Der eigentliche Unterschied
Hier ist was wirklich einen Unterschied macht. Schau dir diese zwei Varianten an:
JUnit:
assertEquals(List.of("apple", "banana"), fruits);
// → expected:<[apple, banana]> but was:<[apple, cherry]>
Truth:
assertThat(fruits).containsExactly("apple", "banana");
// → value of : fruits
// missing (1) : [banana]
// unexpected : [cherry]
// ---
// expected : [apple, banana]
// but was : [apple, cherry]
Truth sagt dir nicht nur, dass etwas falsch ist – es sagt dir was fehlt und was unerwartet auftaucht. In längeren Listen ist das der Unterschied zwischen sofort verstehen und eine Minute suchen.
Collections und Maps
Das ist der Bereich, wo Truth am stärksten glänzt.
Listen:
List<String> fruits = List.of("apple", "banana", "cherry");
// Exakte Übereinstimmung (Reihenfolge egal)
assertThat(fruits).containsExactlyInAnyOrder("cherry", "apple", "banana");
// Enthält mindestens diese Elemente
assertThat(fruits).containsAtLeast("apple", "cherry");
// Enthält keines dieser Elemente
assertThat(fruits).containsNoneOf("mango", "kiwi");
// Reihenfolge prüfen
assertThat(fruits).containsExactly("apple", "banana", "cherry").inOrder();
Maps:
Map<String, Integer> scores = Map.of("alice", 95, "bob", 87);
assertThat(scores).containsKey("alice");
assertThat(scores).containsEntry("alice", 95);
assertThat(scores).doesNotContainKey("charlie");
Optional:
Optional<String> result = findUser("alice");
assertThat(result).isPresent();
assertThat(result).hasValue("alice");
Hinweis: Für Java
Optionalbrauchst du den ImportTruth8.assertThataustruth-java8-extension– oder du nutzt Truth ≥ 1.1, wo es direkt integriert ist.
Anti-Pattern → Best Practice
Viele Teams landen beim gleichen Anti-Pattern: Assertions, die zwar korrekt sind, aber im Fehlerfall nichts sagen.
Anti-Pattern:
assertTrue(user.isActive());
assertFalse(result.isEmpty());
assertEquals("PENDING", order.getStatus());
Das Problem: Wenn assertTrue(user.isActive()) fehlschlägt, bekommst du nur expected: <true> but was: <false>. Kein Kontext, kein Objekt, keine Erklärung.
Best Practice mit Truth:
assertThat(user.isActive()).isTrue();
assertThat(result).isNotEmpty();
assertThat(order.getStatus()).isEqualTo("PENDING");
Immer noch simpel – aber die Fehlermeldung enthält jetzt den Ausdruck, nicht nur den Wert.
Noch besser: assertWithMessage() für kritische Assertions:
assertWithMessage("User sollte nach Aktivierung aktiv sein")
.that(user.isActive())
.isTrue();
Damit steht der Kontext direkt in der Fehlermeldung. Keine Detektivarbeit mehr.
Custom Subjects: Truth für deine Domain-Objekte
Das ist das Feature, das Truth von einfachen Assertion-Libraries unterscheidet. Du kannst eigene Subjects schreiben – typsichere Assertion-Klassen für deine eigenen Objekte.
Angenommen, du hast eine Order-Klasse:
public class Order {
private String status;
private BigDecimal total;
private List<String> items;
// ...
}
Statt überall assertThat(order.getStatus()).isEqualTo(...) zu schreiben, definierst du einmal:
public class OrderSubject extends Subject {
private final Order actual;
public static OrderSubject assertThat(Order order) {
return Truth.assertAbout(orders()).that(order);
}
public static Subject.Factory<OrderSubject, Order> orders() {
return OrderSubject::new;
}
private OrderSubject(FailureMetadata metadata, Order actual) {
super(metadata, actual);
this.actual = actual;
}
public void isPending() {
check("getStatus()").that(actual.getStatus()).isEqualTo("PENDING");
}
public void hasTotal(BigDecimal expected) {
check("getTotal()").that(actual.getTotal()).isEqualTo(expected);
}
public void containsItem(String item) {
check("getItems()").that(actual.getItems()).contains(item);
}
}
Im Test sieht das dann so aus:
import static com.example.OrderSubject.assertThat;
assertThat(order).isPending();
assertThat(order).hasTotal(new BigDecimal("49.99"));
assertThat(order).containsItem("Laptop");
Lesbar, typsicher, wartbar. Und wenn eine Assertion fehlschlägt, zeigt Truth dir genau welche Property und welchen Wert – weil check("getStatus()") das in die Fehlermeldung einbettet.
Wann lohnt sich das? Custom Subjects sind ein Investment. Es lohnt sich, wenn du dieselben Domain-Objekte in vielen Tests assertierst. Für einmalige Checks reicht
assertThat(order.getStatus()).isEqualTo(...).
Wann Truth, wann einfach JUnit?
Truth ist kein Pflichtprogramm. Standard-JUnit-Assertions (assertEquals, assertNotNull) sind für einfache Checks vollkommen ausreichend. Der Wechsel lohnt sich, wenn:
- Du Collections, Maps oder komplexe Objekte assertierst
- Fehlermeldungen in deinen Tests kryptisch sind
- Du Custom Subjects für häufig genutzte Domain-Objekte brauchst
- Dein Team sich häufig fragt “was hat der Test eigentlich erwartet?”
Für simple Boolean-Checks ist assertTrue völlig legitim. Truth macht Tests nicht automatisch besser – aber es macht Fehlermeldungen deutlich besser.
Fazit
Google Truth ist eine der Assertion-Libraries, die man einmal eingebunden hat und nicht mehr missen will. Die Fluent API macht Assertions lesbar, die Fehlermeldungen sparen echte Debugging-Zeit, und Custom Subjects skalieren sauber mit der Codebasis.
Wenn du bisher assertEquals und assertTrue geschrieben hast und dich gelegentlich über kryptische Fehlermeldungen geärgert hast: Das ist der Moment, Truth auszuprobieren. Dependency rein, assertThat importieren, nächsten Test damit schreiben. Der Rest kommt von selbst.
