[go: up one dir, main page]

Truth - Fluent assertions for Java and Android

What is Truth?

Truth is a library for performing assertions in tests:

assertThat(notificationText).contains("testuser@google.com");

Truth is owned and maintained by the Guava team. It is used in the majority of the tests in Google’s own codebase.

Why use an assertion library?

In other words, why depend on a new library when you can use the methods built into your testing framework, like assertEquals?

assertEquals(
    ImmutableMultiset.of("guava", "dagger", "truth", "auto", "caliper"),
    HashMultiset.create(projectsByTeam().get("corelibs")));

Here’s the equivalent using Truth:

assertThat(projectsByTeam())
    .valuesForKey("corelibs")
    .containsExactly("guava", "dagger", "truth", "auto", "caliper");
  • The Truth code is faster to type, especially with autocompletion.
  • The Truth code is easier to read:
    • It has less boilerplate. For example, in the app we’re testing, projectsByTeam() returns a ListMultimap, so projectsByTeam().get(...) will be equal only to another List whose elements are in the same order. We don’t want to test ordering here, so we must convert to Multiset.
    • Putting the actual value first sets the context for the values to come: When the assertion begins “guava, dagger, …,” the reader isn’t sure what is being tested until later: “All projects? Recently released projects? Oh, core-libraries projects.”

Consider also the failure message:

java.lang.AssertionError: expected:<[guava, dagger, truth, auto, caliper]> but was:<[dagger, auto, caliper, guava]>
  at org.junit.Assert.failNotEquals(Assert.java:835) <2 internal calls>
  at com.google.common.truth.example.DemoTest.testBuiltin(DemoTest.java:64) <19 internal calls>

This is fine for a simple assertion, but consider:

  • If there were many values in the collection, figuring out which values were missing (or extra) could be difficult.
  • If the test were parameterized to test for keys other than "corelibs", the JUnit message wouldn’t show which key the assertion failed for.
  • If the resulting list of projects turned out to be empty, the JUnit message wouldn’t show whether the whole multimap was empty or just the "corelibs" collection.

Here’s the message generated by Truth:

value of    : projectsByTeam().valuesForKey(corelibs)
missing (1) : truth
───
expected    : [guava, dagger, truth, auto, caliper]
but was     : [guava, auto, dagger, caliper]
multimap was: {corelibs=[guava, auto, dagger, caliper]}
  at com.google.common.truth.example.DemoTest.testTruth(DemoTest.java:71)

Truth vs. AssertJ

Truth and AssertJ are very similar. This raises the question: Why did we create Truth? The reason is historical: AssertJ didn’t exist when we started Truth. By the time it was created, we’d begun migrating Google code to Truth, and we’d made some design decisions that would be difficult to retrofit onto AssertJ.

Both Truth and AssertJ have their advantages. We prefer Truth for its simpler API:

  • Truth provides fewer assertions, while still covering the most common needs of Google’s codebase. Compare:
  • Truth aims to provide a single way to perform most tasks. This makes tests easier to understand, and it lets us spend more time improving core features.

We also usually prefer Truth’s failure messages (though we find AssertJ’s to often be similar and, in some cases we’re still working on, to even be better).

Additionally, Truth works on Android devices by default, without requiring users to use an older version or import a different class than usual.

Truth vs. Hamcrest

Truth and Hamcrest differ significantly. We prefer Truth because:

  • Truth assertions are made with chained method calls, so IDEs can suggest the assertions appropriate for a given object.
  • Hamcrest is a more general “matching” library, used not only for making assertions but also for setting expectations on mocking frameworks, with matchers composed together in arbitrary ways. But this flexibility requires complex generics and makes it hard for Hamcrest to produce readable failure messages.

How to use Truth

1. Add the appropriate dependency to your build file:

Maven:

<dependency>
  <groupId>com.google.truth</groupId>
  <artifactId>truth</artifactId>
  <version>1.4.4</version>
  <scope>test</scope>
</dependency>

Gradle:

repositories {
  mavenCentral()
}
dependencies {
  testImplementation "com.google.truth:truth:1.4.4"
}

One warning: Truth depends on the “Android” version of Guava, a subset of the “JRE” version. If your project uses the JRE version, be aware that your build system might select the Android version instead. If so, you may see “missing symbol” errors. The easiest fix is usually to add a direct dependency on the newest JRE version of Guava.

Finally, consider configuring your build to use Error Prone. Error Prone checks for many kinds of bugs, including some we’ve seen in usages of Truth.

2. Add static imports for Truth’s entry points:

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;

3. Write a test assertion:

String string = "awesome";
assertThat(string).startsWith("awe");
assertWithMessage("Without me, it's just aweso")
    .that(string)
    .contains("me");

Iterable<Color> googleColors = googleLogo.getColors();
assertThat(googleColors)
    .containsExactly(BLUE, RED, YELLOW, BLUE, GREEN, RED)
    .inOrder();

If you’re using an IDE with autocompletion, it will suggest a list of assertions you can make about the given type. If not, consult the API docs. For example, if you’re looking for assertions about a Map, look at the documentation for MapSubject, available at truth.dev/MapSubject.

More information