Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

Truth is a fluent and flexible open-source testing framework designed to make test assertions and failure messages more readable.

In this article, we’ll explore the key features of the Truth framework and implement examples to showcase its capabilities.

2. Maven Dependencies

First, we need to add the truth and truth-java8-extension to our pom.xml:

<dependency>
    <groupId>com.google.truth</groupId>
    <artifactId>truth</artifactId>
    <version>0.32</version>
</dependency>
<dependency>
    <groupId>com.google.truth.extensions</groupId>
    <artifactId>truth-java8-extension</artifactId>
    <version>0.32</version>
    <scope>test</scope>
</dependency>

You can find the latest versions of truth and truth-java8-extension on Maven Central.

3. Introduction

Truth allows us to write readable assertions and failure messages for a variety of classes:

  • Standard Java – primitives, arrays, strings, objects, collections, throwables, classes, etc.
  • Java 8Optional and Stream instances
  • GuavaOptional, Multimap, Multiset, and Table objects
  • Custom types – by extending the Subject class, as we’ll see later

Through the Truth and Truth8 classes, the library provides utility methods for writing assertions that work on a subject, that’s the value or object under test.

Once the subject is known, Truth can reason at compile time about what propositions are known for that subject. This allows it to return wrappers around our value that declare proposition methods specific to that particular subject.

For example, when asserting on a list, Truth returns an IterableSubject instance defining methods like contains() and containsAnyOf(), among others. When asserting on a Map, it returns a MapSubject that declares methods like containsEntry() and containsKey().

4. Getting Started

To start writing assertions, let’s first import Truth‘s entry points:

import static com.google.common.truth.Truth.*;
import static com.google.common.truth.Truth8.*;

Now, let’s write a simple class that we’ll use in a few of the examples that follow:

public class User {
    private String name = "John Doe";
    private List<String> emails
      = Arrays.asList("[email protected]", "[email protected]");

    public boolean equals(Object obj) {
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }

        User other = (User) obj;
        return Objects.equals(this.name, other.name);
    }

    // standard constructors, getters and setters
}

Notice the custom equals() method, in which we state that two User objects are equal if their names are.

5. Standard Java Assertions

In this section, we’ll see detailed examples of how to write test assertions for standard Java types.

5.1. Object Assertions

Truth provides the Subject wrapper for performing assertions on objects. Subject is also the parent of all other wrappers in the library and declares methods for determining if an Object, in our case a User, is equal to another object:

@Test
public void whenComparingUsers_thenEqual() {
    User aUser = new User("John Doe");
    User anotherUser = new User("John Doe");

    assertThat(aUser).isEqualTo(anotherUser);
}

or if it’s equal to a given object in a list:

@Test
public void whenComparingUser_thenInList() {
    User aUser = new User();

    assertThat(aUser).isIn(Arrays.asList(1, 3, aUser, null));
}

or if it isn’t:

@Test
public void whenComparingUser_thenNotInList() {
    // ...

    assertThat(aUser).isNotIn(Arrays.asList(1, 3, "Three"));
}

if it’s null or not:

@Test
public void whenComparingUser_thenIsNull() {
    User aUser = null;

    assertThat(aUser).isNull();
}

@Test
public void whenComparingUser_thenNotNull() {
    User aUser = new User();

    assertThat(aUser).isNotNull();
}

or if it’s an instance of a particular class:

@Test
public void whenComparingUser_thenInstanceOf() {
    // ...

    assertThat(aUser).isInstanceOf(User.class);
}

There are other assertion methods in the Subject class. To discover them all, refer to the Subject documentation.

In the following sections, we are going to focus on the most relevant methods for each particular type Truth supports. However, keep in mind that all methods in the Subject class can also be applied.

5.2. Integer, Float, and Double Assertions

Integer, Float, and Double instances can be compared for equality:

@Test
public void whenComparingInteger_thenEqual() {
    int anInt = 10;

    assertThat(anInt).isEqualTo(10);
}

if they are bigger:

@Test
public void whenComparingFloat_thenIsBigger() {
    float aFloat = 10.0f;

    assertThat(aFloat).isGreaterThan(1.0f);
}

or smaller:

@Test
public void whenComparingDouble_thenIsSmaller() {
    double aDouble = 10.0f;

    assertThat(aDouble).isLessThan(20.0);
}

Furthermore, Float and Double instances can also be checked to see if they are within an expected precision or not:

@Test
public void whenComparingDouble_thenWithinPrecision() {
    double aDouble = 22.18;

    assertThat(aDouble).isWithin(2).of(23d);
}

@Test
public void whenComparingFloat_thenNotWithinPrecision() {
    float aFloat = 23.04f;

    assertThat(aFloat).isNotWithin(1.3f).of(100f);
}

5.3. BigDecimal Assertions

Besides the common assertions, this type can be compared ignoring its scale:

@Test
public void whenComparingBigDecimal_thenEqualIgnoringScale() {
    BigDecimal aBigDecimal = BigDecimal.valueOf(1000, 3);

    assertThat(aBigDecimal).isEqualToIgnoringScale(new BigDecimal(1.0));
}

5.4. Boolean Assertions

Only two relevant methods are provided, isTrue() and isFalse():

@Test
public void whenCheckingBoolean_thenTrue() {
    boolean aBoolean = true;

    assertThat(aBoolean).isTrue();
}

5.5. String Assertions

We can test whether a String starts with a particular text:

@Test
public void whenCheckingString_thenStartsWith() {
    String aString = "This is a string";

    assertThat(aString).startsWith("This");
}

In addition, we can check if the string contains a given String, if it ends with an expected value or whether it’s empty. Test cases for these and other methods are available in the source code.

5.6. Array Assertions

We can check Arrays to see if they are equal to other arrays:

@Test
public void whenComparingArrays_thenEqual() {
    String[] firstArrayOfStrings = { "one", "two", "three" };
    String[] secondArrayOfStrings = { "one", "two", "three" };

    assertThat(firstArrayOfStrings).isEqualTo(secondArrayOfStrings);
}

or if they are empty:

@Test
public void whenCheckingArray_thenEmpty() {
    Object[] anArray = {};

    assertThat(anArray).isEmpty();
}

5.7. Comparable Assertions

Besides testing whether a Comparable is greater than or less than another instance, we can check to see if they are at least a given value:

@Test
public void whenCheckingComparable_thenAtLeast() {
    Comparable<Integer> aComparable = 5;

    assertThat(aComparable).isAtLeast(1);
}

Also, we can test whether they are within a particular range:

@Test
public void whenCheckingComparable_thenInRange() {
    // ...

    assertThat(aComparable).isIn(Range.closed(1, 10));
}

or in a particular list:

@Test
public void whenCheckingComparable_thenInList() {
    // ...

    assertThat(aComparable).isIn(Arrays.asList(4, 5, 6));
}

We can also test if two Comparable instances are equivalent according to the class’s compareTo() method.

First, let’s modify our User class to implement the Comparable interface:

public class User implements Comparable<User> {
    // ...
    
    public int compareTo(User o) {
        return this.getName().compareToIgnoreCase(o.getName());
    }
}

Now, let’s assert that two users with the same name are equivalent:

@Test
public void whenComparingUsers_thenEquivalent() {
    User aUser = new User();
    aUser.setName("John Doe");

    User anotherUser = new User();
    anotherUser.setName("john doe");

    assertThat(aUser).isEquivalentAccordingToCompareTo(anotherUser);
}

5.8. Iterable Assertions

In addition to asserting the size of an Iterable instance, whether it’s empty or has no duplicates, most typical assertions on an Iterable are that it contains some element:

@Test
public void whenCheckingIterable_thenContains() {
    List<Integer> aList = Arrays.asList(4, 5, 6);

    assertThat(aList).contains(5);
}

that it contains any element of another Iterable:

@Test
public void whenCheckingIterable_thenContainsAnyInList() {
    List<Integer> aList = Arrays.asList(1, 2, 3);

    assertThat(aList).containsAnyIn(Arrays.asList(1, 5, 10));
}

and that the subject has the same elements, in the same order, like another:

@Test
public void whenCheckingIterable_thenContainsExactElements() {
    List<String> aList = Arrays.asList("10", "20", "30");
    List<String> anotherList = Arrays.asList("10", "20", "30");

    assertThat(aList)
      .containsExactlyElementsIn(anotherList)
      .inOrder();
}

and if it’s ordered using a custom comparator:

@Test
public void givenComparator_whenCheckingIterable_thenOrdered() {
    Comparator<String> aComparator
      = (a, b) -> new Float(a).compareTo(new Float(b));

    List<String> aList = Arrays.asList("1", "012", "0020", "100");

    assertThat(aList).isOrdered(aComparator);
}

5.9. Map Assertions

In addition to asserting that a Map instance is empty or not, or has a specific size; we can check if it has a specific entry:

@Test
public void whenCheckingMap_thenContainsEntry() {
    Map<String, Object> aMap = new HashMap<>();
    aMap.put("one", 1L);

    assertThat(aMap).containsEntry("one", 1L);
}

if it has a specific key:

@Test
public void whenCheckingMap_thenContainsKey() {
    // ...

    assertThat(map).containsKey("one");
}

or if it has the same entries as another Map:

@Test
public void whenCheckingMap_thenContainsEntries() {
    Map<String, Object> aMap = new HashMap<>();
    aMap.put("first", 1L);
    aMap.put("second", 2.0);
    aMap.put("third", 3f);

    Map<String, Object> anotherMap = new HashMap<>(aMap);

    assertThat(aMap).containsExactlyEntriesIn(anotherMap);
}

5.10. Exception Assertions

Only two methods of importance are provided for Exception objects.

We can write assertions addressed to the cause of the exception:

@Test
public void whenCheckingException_thenInstanceOf() {
    Exception anException
      = new IllegalArgumentException(new NumberFormatException());

    assertThat(anException)
      .hasCauseThat()
      .isInstanceOf(NumberFormatException.class);
}

or to its message:

@Test
public void whenCheckingException_thenCauseMessageIsKnown() {
    Exception anException
      = new IllegalArgumentException("Bad value");

    assertThat(anException)
      .hasMessageThat()
      .startsWith("Bad");
}

5.11. Class Assertions

There’s only one important method for Class assertions with which we can test whether a class is assignable to another:

@Test
public void whenCheckingClass_thenIsAssignable() {
    Class<Double> aClass = Double.class;

    assertThat(aClass).isAssignableTo(Number.class);
}

6. Java 8 Assertions

Optional and Stream are the only two Java 8 types that Truth supports.

6.1. Optional Assertions

There are three important methods to verify an Optional.

We can test whether it has a particular value:

@Test
public void whenCheckingJavaOptional_thenHasValue() {
    Optional<Integer> anOptional = Optional.of(1);

    assertThat(anOptional).hasValue(1);
}

if the value is present:

@Test
public void whenCheckingJavaOptional_thenPresent() {
    Optional<String> anOptional = Optional.of("Baeldung");

    assertThat(anOptional).isPresent();
}

or if the value is not present:

@Test
public void whenCheckingJavaOptional_thenEmpty() {
    Optional anOptional = Optional.empty();

    assertThat(anOptional).isEmpty();
}

6.2. Stream Assertions

Assertions for a Stream are very similar to the ones for an Iterable.

For example, we can test if a particular Stream contains all objects of an Iterable in the same order:

@Test
public void whenCheckingStream_thenContainsInOrder() {
    Stream<Integer> anStream = Stream.of(1, 2, 3);

    assertThat(anStream)
      .containsAllOf(1, 2, 3)
      .inOrder();
}

For more examples, please refer to the Iterable Assertions section.

7. Guava Assertions

In this section, we’ll see examples of assertions for the supported Guava types in Truth.

7.1. Optional Assertions

There are also three important assertion methods for a Guava Optional. The hasValue() and isPresent() methods behave exactly as with a Java 8 Optional.

But instead of isEmpty() to assert that an Optional is not present, we use isAbsent():

@Test
public void whenCheckingGuavaOptional_thenIsAbsent() {
    Optional anOptional = Optional.absent();

    assertThat(anOptional).isAbsent();
}

7.2. Multimap Assertions

Multimap and standard Map assertions are very similar.

One notable difference is that we can get the multiple values of a key within a Multimap and make assertions on those values.

Here’s an example that tests if the values of the “one” key have a size of two:

@Test
public void whenCheckingGuavaMultimap_thenExpectedSize() {
    Multimap<String, Object> aMultimap = ArrayListMultimap.create();
    aMultimap.put("one", 1L);
    aMultimap.put("one", 2.0);

    assertThat(aMultimap)
      .valuesForKey("one")
      .hasSize(2);
}

For more examples, please refer to the Map Assertions section.

7.3. Multiset Assertions

Assertions for Multiset objects include the ones for an Iterable and one extra method to verify if a key has a particular number of occurrences:

@Test
public void whenCheckingGuavaMultiset_thenExpectedCount() {
    TreeMultiset<String> aMultiset = TreeMultiset.create();
    aMultiset.add("baeldung", 10);

    assertThat(aMultiset).hasCount("baeldung", 10);
}

7.4. Table Assertions

Besides checking its size or where it’s empty, we can check a Table to verify if it contains a particular mapping for a given row and column:

@Test
public void whenCheckingGuavaTable_thenContains() {
    Table<String, String, String> aTable = TreeBasedTable.create();
    aTable.put("firstRow", "firstColumn", "baeldung");

    assertThat(aTable).contains("firstRow", "firstColumn");
}

or if it contains a particular cell:

@Test
public void whenCheckingGuavaTable_thenContainsCell() {
    Table<String, String, String> aTable = getDummyGuavaTable();

    assertThat(aTable).containsCell("firstRow", "firstColumn", "baeldung");
}

Furthermore, we can check if it contains a given row, column, or value. See the source code for the relevant test cases.

8. Custom Failure Messages and Labels

When an assertion fails, Truth displays very readable messages denoting exactly what went wrong. However, sometimes is necessary to add more information to those messages to provide more details about what happened.

Truth allows us to customize those failure messages:

@Test
public void whenFailingAssertion_thenCustomMessage() {
    assertWithMessage("TEST-985: Secret user subject was NOT null!")
      .that(new User())
      .isNull();
}

After running the test, we get the following output:

TEST-985: Secret user subject was NOT null!:
  Not true that <com.baeldung.testing.truth.User@ae805d5e> is null

Also, we can add a custom label that gets displayed before our subject in error messages. This may come in handy when an object does not have a helpful string representation:

@Test
public void whenFailingAssertion_thenMessagePrefix() {
    User aUser = new User();

    assertThat(aUser)
      .named("User [%s]", aUser.getName())
      .isNull();
}

If we run the test, we can see the following output:

Not true that User [John Doe]
  (<com.baeldung.testing.truth.User@ae805d5e>) is null

9. Extensions

Extending Truth means we can add support for custom types. To do this, we need to create a class that:

  • extends the Subject class or one of its subclasses
  • defines a constructor that accepts two arguments – a FailureStrategy and an instance of our custom type
  • declares a field of SubjectFactory type, which Truth will use to create instances of our custom subject
  • implements a static assertThat() method that accepts our custom type
  • exposes our test assertion API

Now that we know how to extend Truth, let’s create a class that adds support for objects of type User:

public class UserSubject
  extends ComparableSubject<UserSubject, User> {

    private UserSubject(
      FailureStrategy failureStrategy, User target) {
        super(failureStrategy, target);
    }

    private static final
      SubjectFactory<UserSubject, User> USER_SUBJECT_FACTORY
      = new SubjectFactory<UserSubject, User>() {

        public UserSubject getSubject(
          FailureStrategy failureStrategy, User target) {
            return new UserSubject(failureStrategy, target);
        }
    };

    public static UserSubject assertThat(User user) {
        return Truth.assertAbout(USER_SUBJECT_FACTORY).that(user);
    }

    public void hasName(String name) {
        if (!actual().getName().equals(name)) {
            fail("has name", name);
        }
    }

    public void hasNameIgnoringCase(String name) {
        if (!actual().getName().equalsIgnoreCase(name)) {
            fail("has name ignoring case", name);
        }
    }

    public IterableSubject emails() {
        return Truth.assertThat(actual().getEmails());
    }
}

Now, we can statically import the assertThat() method of our custom subject and write some tests:

@Test
public void whenCheckingUser_thenHasName() {
    User aUser = new User();

    assertThat(aUser).hasName("John Doe");
}

@Test
public void whenCheckingUser_thenHasNameIgnoringCase() {
    // ...

    assertThat(aUser).hasNameIgnoringCase("john doe");
}

@Test
public void givenUser_whenCheckingEmails_thenExpectedSize() {
    // ...

    assertThat(aUser)
      .emails()
      .hasSize(2);
}

10. Conclusion

In this tutorial, we explored the possibilities Truth gives us to write more readable tests and failure messages.

We showcased the most popular assertion methods for supported Java and Guava types, customized failure messages, and extended Truth with custom subjects.

As always, complete source code for this article can be found over on Github.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.