You write a test. The test fails. You stare at the error message - and only after ten seconds do you understand what actually went wrong.
expected:<[foo]> but was:<[bar]>
Thanks, JUnit. Very insightful.
This is not a marginal problem. Unreadable error messages cost real time, especially in larger codebases where a red test is not always obvious. Google Truth is an assertion library that solves exactly that - with a Fluent API that remains clear in both code and error messages.
What is Google Truth?
Google Truth is an open source assertion library for Java developed by Google. It is not a testing framework - not a replacement for JUnit or TestNG - but a replacement for assertEquals, assertTrue and the like.
The goal is simple: Assertions should read like natural language, and error messages should immediately tell you what’s wrong - without interpretation.
Truth has been used in Google’s internal codebase for years and is being actively developed. It can be integrated into Java projects in minutes.
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'
You need the import in the test:
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
No further setup. Truth works out-of-the-box with JUnit 4, JUnit 5 and TestNG.
The core concept: Subjects
The entry point is always assertThat(). What comes next depends on the type of your object - Truth automatically selects the appropriate Subject.
A Subject is a type-safe wrapper class that offers exactly the assertions that make sense for this type. StringSubject has different methods than IterableSubject or 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();
That almost reads like a sentence. And that’s exactly how the error message reads when something fails.
Error messages: The real difference
Here’s what really makes a difference. Check out these two variants:
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 doesn’t just tell you something is wrong - it tells you what is missing and what shows up unexpectedly. In longer lists, this is the difference between understanding immediately and searching for a minute.
Collections und Maps
This is where Truth shines the most.
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");
Note: For Java
Optionalyou need the importTruth8.assertThatfromtruth-java8-extension- or you use Truth ≥ 1.1, where it is integrated directly.
Anti-Pattern → Best Practice
Many teams end up with the same anti-pattern: assertions that are correct but say nothing in the event of an error.
Anti-Pattern:
assertTrue(user.isActive());
assertFalse(result.isEmpty());
assertEquals("PENDING", order.getStatus());
The problem: If assertTrue(user.isActive()) fails, you just get expected: <true> but was: <false>. No context, no object, no explanation.
Best Practice mit Truth:
assertThat(user.isActive()).isTrue();
assertThat(result).isNotEmpty();
assertThat(order.getStatus()).isEqualTo("PENDING");
Still simple - but the error message now includes the expression, not just the value.
Even better: assertWithMessage() for critical assertions:
assertWithMessage("User sollte nach Aktivierung aktiv sein")
.that(user.isActive())
.isTrue();
This means the context is directly in the error message. No more detective work.
Custom Subjects: Truth for your domain objects
This is the feature that differentiates Truth from simple assertion libraries. You can write your own Subjects – type-safe assertion classes for your own objects.
Suppose you have an Order class:
public class Order {
private String status;
private BigDecimal total;
private List<String> items;
// ...
}
Instead of writing assertThat(order.getStatus()).isEqualTo(...) everywhere, define once:
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);
}
}
In the test it looks like this:
import static com.example.OrderSubject.assertThat;
assertThat(order).isPending();
assertThat(order).hasTotal(new BigDecimal("49.99"));
assertThat(order).containsItem("Laptop");
Readable, type-safe, maintainable. And if an assertion fails, Truth shows you exactly which property and which value - because check("getStatus()") embeds that in the error message.
When is it worth it? Custom subjects are an investment. It’s worth it if you assert the same domain objects in many tests. For one-time checks
assertThat(order.getStatus()).isEqualTo(...)is sufficient.
If Truth, if simple JUnit?
Truth is not a mandatory program. Standard JUnit assertions (assertEquals, assertNotNull) are completely sufficient for simple checks. The change is worth it if:
- You assert collections, maps or complex objects
- Error messages in your tests are cryptic
- You need custom subjects for frequently used domain objects
- Your team often asks themselves “what did the test actually expect?”
For simple Boolean checks, assertTrue is completely legitimate. Truth doesn’t automatically make tests better - but it does make error reporting significantly better.
Conclusion
Google Truth is one of those assertion libraries that you have integrated once and don’t want to be without. The Fluent API makes assertions readable, the error messages save real debugging time, and custom subjects scale cleanly with the codebase.
If you’ve written assertEquals and assertTrue and been occasionally annoyed by cryptic error messages: this is the time to try Truth. Dependency in, import assertThat, write next test with it. The rest comes by itself.
![[EN] Google Truth: Assertions you can read](/images/google-truth-header.jpg)