Test con Google Truth

1. Panoramica

La verità è un framework di test open source fluido e flessibile progettato per rendere più leggibili le asserzioni di test ei messaggi di errore.

In questo articolo, esploreremo le funzionalità chiave del framework Truth e implementeremo esempi per mostrare le sue capacità.

2. Dipendenze di Maven

Per prima cosa, dobbiamo aggiungere la verità e l' estensione truth-java8 al nostro pom.xml:

 com.google.truth truth 0.32   com.google.truth.extensions truth-java8-extension 0.32 test 

È possibile trovare le ultime versioni di verità e verità-java8-extension su Maven Central.

3. Introduzione

La verità ci consente di scrivere asserzioni leggibili e messaggi di errore per una varietà di classi:

  • Java standard : primitive, array, stringhe, oggetti, raccolte, prodotti lanciabili, classi, ecc.
  • Java 8 : istanze opzionali e Stream
  • Guava - opzionale , Multimap , Multiset , e Tabella oggetti
  • Tipi personalizzati : estendendo la classe Subject , come vedremo più avanti

Attraverso le classi Truth e Truth8 , la libreria fornisce metodi di utilità per scrivere asserzioni che funzionano su un argomento , che è il valore o l'oggetto sotto test.

Una volta che l'argomento è noto, la Verità può ragionare in fase di compilazione su quali proposizioni sono note per quell'argomento . Ciò gli consente di restituire wrapper attorno al nostro valore che dichiarano metodi di proposizione specifici per quel particolare argomento.

Ad esempio, quando si asserisce su un elenco, Truth restituisce un'istanza di IterableSubject che definisce metodi come contains () e containsAnyOf () , tra gli altri. Quando si asserisce su una mappa , restituisce un MapSubject che dichiara metodi come containsEntry () e containsKey () .

4. Introduzione

Per iniziare a scrivere asserzioni, importiamo prima i punti di ingresso di Truth :

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

Ora, scriviamo una semplice classe che useremo in alcuni degli esempi che seguono:

public class User { private String name = "John Doe"; private List 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 }

Notare il metodo personalizzato equals () , in cui si afferma che due oggetti User sono uguali se i loro nomi lo sono.

5. Asserzioni Java standard

In questa sezione, vedremo esempi dettagliati su come scrivere asserzioni di test per i tipi Java standard.

5.1. Asserzioni di oggetti

La verità fornisce il wrapper del soggetto per eseguire asserzioni sugli oggetti. Il Soggetto è anche il genitore di tutti gli altri wrapper nella libreria e dichiara i metodi per determinare se un Oggetto , nel nostro caso un Utente , è uguale a un altro oggetto:

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

o se è uguale a un dato oggetto in una lista:

@Test public void whenComparingUser_thenInList() { User aUser = new User(); assertThat(aUser).isIn(Arrays.asList(1, 3, aUser, null)); }

o se non lo è:

@Test public void whenComparingUser_thenNotInList() { // ... assertThat(aUser).isNotIn(Arrays.asList(1, 3, "Three")); }

se è nullo o no:

@Test public void whenComparingUser_thenIsNull() { User aUser = null; assertThat(aUser).isNull(); } @Test public void whenComparingUser_thenNotNull() { User aUser = new User(); assertThat(aUser).isNotNull(); }

o se è un'istanza di una particolare classe:

@Test public void whenComparingUser_thenInstanceOf() { // ... assertThat(aUser).isInstanceOf(User.class); }

Esistono altri metodi di asserzione nella classe Subject . Per scoprirli tutti, fare riferimento alla documentazione del Soggetto .

Nelle sezioni seguenti, ci concentreremo sui metodi più rilevanti per ogni tipo particolare supportato da Truth . Tuttavia, tieni presente che possono essere applicati anche tutti i metodi nella classe Subject .

5.2. Integer , Float e Double Assertions

Le istanze Integer , Float e Double possono essere confrontate per l'uguaglianza:

@Test public void whenComparingInteger_thenEqual() { int anInt = 10; assertThat(anInt).isEqualTo(10); }

se sono più grandi:

@Test public void whenComparingFloat_thenIsBigger() { float aFloat = 10.0f; assertThat(aFloat).isGreaterThan(1.0f); }

o più piccolo:

@Test public void whenComparingDouble_thenIsSmaller() { double aDouble = 10.0f; assertThat(aDouble).isLessThan(20.0); }

Inoltre , le istanze Float e Double possono anche essere controllate per vedere se rientrano o meno in una precisione prevista:

@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. Asserzioni BigDecimal

Oltre alle affermazioni comuni, questo tipo può essere confrontato ignorando la sua scala:

@Test public void whenComparingBigDecimal_thenEqualIgnoringScale() { BigDecimal aBigDecimal = BigDecimal.valueOf(1000, 3); assertThat(aBigDecimal).isEqualToIgnoringScale(new BigDecimal(1.0)); }

5.4. Asserzioni booleane

Vengono forniti solo due metodi rilevanti, isTrue () e isFalse () :

@Test public void whenCheckingBoolean_thenTrue() { boolean aBoolean = true; assertThat(aBoolean).isTrue(); }

5.5. Asserzioni di stringa

Possiamo verificare se una stringa inizia con un testo particolare:

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

Inoltre, possiamo controllare se la stringa contiene una data stringa, se termina con un valore atteso o se è vuota. I casi di test per questi e altri metodi sono disponibili nel codice sorgente.

5.6. Asserzioni array

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 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 { // ... 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 aList = Arrays.asList(4, 5, 6); assertThat(aList).contains(5); }

that it contains any element of another Iterable:

@Test public void whenCheckingIterable_thenContainsAnyInList() { List 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 aList = Arrays.asList("10", "20", "30"); List 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 aComparator = (a, b) -> new Float(a).compareTo(new Float(b)); List 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 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 aMap = new HashMap(); aMap.put("first", 1L); aMap.put("second", 2.0); aMap.put("third", 3f); Map 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 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 anOptional = Optional.of(1); assertThat(anOptional).hasValue(1); }

if the value is present:

@Test public void whenCheckingJavaOptional_thenPresent() { Optional 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 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 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 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 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 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 <[email protected]> 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] (<[email protected]>) 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 { private UserSubject( FailureStrategy failureStrategy, User target) { super(failureStrategy, target); } private static final SubjectFactory USER_SUBJECT_FACTORY = new SubjectFactory() { 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 questo tutorial, abbiamo esplorato le possibilità che Truth ci offre per scrivere test e messaggi di errore più leggibili.

Abbiamo mostrato i metodi di asserzione più popolari per i tipi Java e Guava supportati, messaggi di errore personalizzati e Verità estesa con oggetti personalizzati.

Come sempre, il codice sorgente completo per questo articolo può essere trovato su Github.