Test con Hamcrest

1. Panoramica

Hamcrest è il noto framework utilizzato per i test di unità nell'ecosistema Java. È impacchettato in JUnit e, in poche parole, utilizza predicati esistenti, chiamati classi di corrispondenza, per fare asserzioni.

In questo tutorial, esploreremo l'API Hamcrest e impareremo come trarne vantaggio per scrivere unit test più ordinati e intuitivi per il nostro software.

2. Configurazione Hamcrest

Possiamo usare Hamcrest con Maven aggiungendo la seguente dipendenza al nostro file pom.xml :

 org.hamcrest hamcrest-all 1.3 

L'ultima versione di questa libreria è sempre disponibile qui.

3. Un test di esempio

Hamcrest è comunemente usato con junit e altri framework di test per fare asserzioni. In particolare, invece di utilizzare i numerosi metodi assert di junit , utilizziamo solo la singola dichiarazione assertThat dell'API con i corrispondenti appropriati.

Diamo un'occhiata a un esempio che testa due String per l'uguaglianza indipendentemente dal caso. Questo dovrebbe darci un'idea chiara di come Hamcrest si adatta a un metodo di test:

public class StringMatcherTest { @Test public void given2Strings_whenEqual_thenCorrect() { String a = "foo"; String b = "FOO"; assertThat(a, equalToIgnoringCase(b)); } }

Nelle sezioni seguenti daremo uno sguardo a molti altri matcher comuni offerti da Hamcrest .

4. Object Matcher

Hamcrest fornisce abbinamenti per fare asserzioni su oggetti Java arbitrari.

Per affermare che il metodo toString di un oggetto restituisce una stringa specificata :

@Test public void givenBean_whenToStringReturnsRequiredString_thenCorrect(){ Person person=new Person("Barrack", "Washington"); String str=person.toString(); assertThat(person,hasToString(str)); }

Possiamo anche verificare che una classe sia una sottoclasse di un'altra:

@Test public void given2Classes_whenOneInheritsFromOther_thenCorrect(){ assertThat(Cat.class,typeCompatibleWith(Animal.class)); } }

5. The Bean Matcher

Possiamo usare il Bean Matcher di Hamcrest per ispezionare le proprietà di un Java bean.

Assumi il seguente bean Person :

public class Person { String name; String address; public Person(String personName, String personAddress) { name = personName; address = personAddress; } }

Possiamo controllare se il bean ha la proprietà, nome in questo modo:

@Test public void givenBean_whenHasValue_thenCorrect() { Person person = new Person("Baeldung", 25); assertThat(person, hasProperty("name")); }

Possiamo anche verificare se Persona ha la proprietà dell'indirizzo , inizializzata a New York:

@Test public void givenBean_whenHasCorrectValue_thenCorrect() { Person person = new Person("Baeldung", "New York"); assertThat(person, hasProperty("address", equalTo("New York"))); }

Possiamo anche verificare se due oggetti Person sono costruiti con gli stessi valori:

@Test public void given2Beans_whenHavingSameValues_thenCorrect() { Person person1 = new Person("Baeldung", "New York"); Person person2 = new Person("Baeldung", "New York"); assertThat(person1, samePropertyValuesAs(person2)); } 

6. The Collection Matcher

Hamcrest fornisce abbinamenti per l'ispezione delle raccolte .

Semplice controllo per scoprire se una raccolta è vuota:

@Test public void givenCollection_whenEmpty_thenCorrect() { List emptyList = new ArrayList(); assertThat(emptyList, empty()); }

Per controllare le dimensioni di una collezione:

@Test public void givenAList_whenChecksSize_thenCorrect() { List hamcrestMatchers = Arrays.asList( "collections", "beans", "text", "number"); assertThat(hamcrestMatchers, hasSize(4)); }

Possiamo anche usarlo per affermare che un array ha una dimensione richiesta:

@Test public void givenArray_whenChecksSize_thenCorrect() { String[] hamcrestMatchers = { "collections", "beans", "text", "number" }; assertThat(hamcrestMatchers, arrayWithSize(4)); }

Per verificare se una raccolta contiene determinati membri, indipendentemente dall'ordine:

@Test public void givenAListAndValues_whenChecksListForGivenValues_thenCorrect() { List hamcrestMatchers = Arrays.asList( "collections", "beans", "text", "number"); assertThat(hamcrestMatchers, containsInAnyOrder("beans", "text", "collections", "number")); }

Per affermare ulteriormente che i membri della raccolta sono in un dato ordine:

@Test public void givenAListAndValues_whenChecksListForGivenValuesWithOrder_thenCorrect() { List hamcrestMatchers = Arrays.asList( "collections", "beans", "text", "number"); assertThat(hamcrestMatchers, contains("collections", "beans", "text", "number")); }

Per verificare se un array ha un singolo elemento dato:

@Test public void givenArrayAndValue_whenValueFoundInArray_thenCorrect() { String[] hamcrestMatchers = { "collections", "beans", "text", "number" }; assertThat(hamcrestMatchers, hasItemInArray("text")); }

Possiamo anche usare un matcher alternativo per lo stesso test:

@Test public void givenValueAndArray_whenValueIsOneOfArrayElements_thenCorrect() { String[] hamcrestMatchers = { "collections", "beans", "text", "number" }; assertThat("text", isOneOf(hamcrestMatchers)); }

O ancora possiamo fare lo stesso con un matcher diverso in questo modo:

@Test public void givenValueAndArray_whenValueFoundInArray_thenCorrect() { String[] array = new String[] { "collections", "beans", "text", "number" }; assertThat("beans", isIn(array)); }

Possiamo anche verificare se l'array contiene determinati elementi indipendentemente dall'ordine:

@Test public void givenArrayAndValues_whenValuesFoundInArray_thenCorrect() { String[] hamcrestMatchers = { "collections", "beans", "text", "number" }; assertThat(hamcrestMatchers, arrayContainingInAnyOrder("beans", "collections", "number", "text")); }

Per verificare se l'array contiene elementi dati ma nell'ordine dato:

@Test public void givenArrayAndValues_whenValuesFoundInArrayInOrder_thenCorrect() { String[] hamcrestMatchers = { "collections", "beans", "text", "number" }; assertThat(hamcrestMatchers, arrayContaining("collections", "beans", "text", "number")); }

Quando la nostra raccolta è una mappa, possiamo utilizzare i seguenti abbinamenti in queste rispettive funzioni:

Per verificare se contiene una determinata chiave:

@Test public void givenMapAndKey_whenKeyFoundInMap_thenCorrect() { Map map = new HashMap(); map.put("blogname", "baeldung"); assertThat(map, hasKey("blogname")); }

e un dato valore:

@Test public void givenMapAndValue_whenValueFoundInMap_thenCorrect() { Map map = new HashMap(); map.put("blogname", "baeldung"); assertThat(map, hasValue("baeldung")); }

e infine una data voce (chiave, valore):

@Test public void givenMapAndEntry_whenEntryFoundInMap_thenCorrect() { Map map = new HashMap(); map.put("blogname", "baeldung"); assertThat(map, hasEntry("blogname", "baeldung")); }

7. The Number Matcher

I Numero matchers vengono utilizzati per eseguire affermazioni sulle variabili del numero di classe.

Per verificare una condizione maggiore di:

@Test public void givenAnInteger_whenGreaterThan0_thenCorrect() { assertThat(1, greaterThan(0)); }

Per controllare maggiore o uguale a condizione:

@Test public void givenAnInteger_whenGreaterThanOrEqTo5_thenCorrect() { assertThat(5, greaterThanOrEqualTo(5)); }

Per controllare meno della condizione:

@Test public void givenAnInteger_whenLessThan0_thenCorrect() { assertThat(-1, lessThan(0)); }

Per controllare meno o uguale a condizione:

@Test public void givenAnInteger_whenLessThanOrEqTo5_thenCorrect() { assertThat(-1, lessThanOrEqualTo(5)); }

Per controllare closeTo condition:

@Test public void givenADouble_whenCloseTo_thenCorrect() { assertThat(1.2, closeTo(1, 0.5)); }

Let's pay close attention to the last matcher, closeTo. The first argument, the operand, is the one to which the target is compared and the second argument is the allowable deviation from the operand . This means that if the target is operand+deviation or operand-deviation, then the test will pass.

8. The Text Matcher

Assertion on Strings is made easier, neater and more intuitive with Hamcrest‘s text matchers. We are going to take a look at them in this section.

To check if a String is empty:

@Test public void givenString_whenEmpty_thenCorrect() { String str = ""; assertThat(str, isEmptyString()); }

To check if a String is empty or null:

@Test public void givenString_whenEmptyOrNull_thenCorrect() { String str = null; assertThat(str, isEmptyOrNullString()); }

To check for equality of two Strings while ignoring white space:

@Test public void given2Strings_whenEqualRegardlessWhiteSpace_thenCorrect() { String str1 = "text"; String str2 = " text "; assertThat(str1, equalToIgnoringWhiteSpace(str2)); }

We can also check for the presence of one or more sub-strings in a given String in a given order:

@Test public void givenString_whenContainsGivenSubstring_thenCorrect() { String str = "calligraphy"; assertThat(str, stringContainsInOrder(Arrays.asList("call", "graph"))); }

Finally, we can check for equality of two Strings regardless of case:

@Test public void given2Strings_whenEqual_thenCorrect() { String a = "foo"; String b = "FOO"; assertThat(a, equalToIgnoringCase(b)); }

9. The Core API

The Hamcrest core API is to be used by third-party framework providers. However, it offers us some great constructs to make our unit tests more readable and also some core matchers that can be used just as easily.

Readability with the is construct on a matcher:

@Test public void given2Strings_whenIsEqualRegardlessWhiteSpace_thenCorrect() { String str1 = "text"; String str2 = " text "; assertThat(str1, is(equalToIgnoringWhiteSpace(str2))); }

The is construct on a simple data type:

@Test public void given2Strings_whenIsEqual_thenCorrect() { String str1 = "text"; String str2 = "text"; assertThat(str1, is(str2)); }

Negation with the not construct on a matcher:

@Test public void given2Strings_whenIsNotEqualRegardlessWhiteSpace_thenCorrect() { String str1 = "text"; String str2 = " texts "; assertThat(str1, not(equalToIgnoringWhiteSpace(str2))); }

The not construct on a simple data type:

@Test public void given2Strings_whenNotEqual_thenCorrect() { String str1 = "text"; String str2 = "texts"; assertThat(str1, not(str2)); }

Check if a String contains a given sub-string:

@Test public void givenAStrings_whenContainsAnotherGivenString_thenCorrect() { String str1 = "calligraphy"; String str2 = "call"; assertThat(str1, containsString(str2)); }

Check if a String starts with given sub-string:

@Test public void givenAString_whenStartsWithAnotherGivenString_thenCorrect() { String str1 = "calligraphy"; String str2 = "call"; assertThat(str1, startsWith(str2)); }

Check if a String ends with given sub-string:

@Test public void givenAString_whenEndsWithAnotherGivenString_thenCorrect() { String str1 = "calligraphy"; String str2 = "phy"; assertThat(str1, endsWith(str2)); }

Check if two Objects are of the same instance:

@Test public void given2Objects_whenSameInstance_thenCorrect() { Cat cat=new Cat(); assertThat(cat, sameInstance(cat)); }

Check if an Object is an instance of a given class:

@Test public void givenAnObject_whenInstanceOfGivenClass_thenCorrect() { Cat cat=new Cat(); assertThat(cat, instanceOf(Cat.class)); }

Check if all members of a Collection meet a condition:

@Test public void givenList_whenEachElementGreaterThan0_thenCorrect() { List list = Arrays.asList(1, 2, 3); int baseCase = 0; assertThat(list, everyItem(greaterThan(baseCase))); }

Check that a String is not null:

@Test public void givenString_whenNotNull_thenCorrect() { String str = "notnull"; assertThat(str, notNullValue()); }

Chain conditions together, test passes when target meets any of the conditions, similar to logical OR:

@Test public void givenString_whenMeetsAnyOfGivenConditions_thenCorrect() { String str = "calligraphy"; String start = "call"; String end = "foo"; assertThat(str, anyOf(startsWith(start), containsString(end))); }

Chain conditions together, test passes only when target meets all conditions, similar to logical AND:

@Test public void givenString_whenMeetsAllOfGivenConditions_thenCorrect() { String str = "calligraphy"; String start = "call"; String end = "phy"; assertThat(str, allOf(startsWith(start), endsWith(end))); }

10. A Custom Matcher

We can define our own matcher by extending TypeSafeMatcher. In this section, we will create a custom matcher which allows a test to pass only when the target is a positive integer.

public class IsPositiveInteger extends TypeSafeMatcher { public void describeTo(Description description) { description.appendText("a positive integer"); } @Factory public static Matcher isAPositiveInteger() { return new IsPositiveInteger(); } @Override protected boolean matchesSafely(Integer integer) { return integer > 0; } }

We need only to implement the matchSafely method which checks that the target is indeed a positive integer and the describeTo method which produces a failure message in case the test does not pass.

Here is a test that uses our new custom matcher:

@Test public void givenInteger_whenAPositiveValue_thenCorrect() { int num = 1; assertThat(num, isAPositiveInteger()); }

and here is a failure message we get since we have passed in a non-positive integer:

java.lang.AssertionError: Expected: a positive integer but: was 

11. Conclusion

In this tutorial, we have explored the Hamcrest API and learnt how we can write better and more maintainable unit tests with it.

L'implementazione completa di tutti questi esempi e frammenti di codice può essere trovata nel mio progetto GitHub Hamcrest.