1. Información general
Hamcrest es el marco conocido utilizado para pruebas unitarias en el ecosistema Java. Está incluido en JUnit y, en pocas palabras, utiliza predicados existentes, llamados clases de emparejamiento, para hacer afirmaciones.
En este tutorial, exploraremos la API de Hamcrest y aprenderemos cómo aprovecharla para escribir pruebas unitarias más ordenadas e intuitivas para nuestro software.
2. Configuración de Hamcrest
Podemos usar Hamcrest con maven agregando la siguiente dependencia a nuestro archivo pom.xml :
org.hamcrest hamcrest-all 1.3
La última versión de esta biblioteca siempre se puede encontrar aquí.
3. Un ejemplo de prueba
Hamcrest se usa comúnmente con junit y otros marcos de prueba para hacer afirmaciones. En concreto, en lugar de utilizar JUnit 's numerosas aserciones métodos, que solo uso de la API assertThat declaración con comparadores apropiados.
Veamos un ejemplo que prueba la igualdad de dos cadenas independientemente del caso. Esto debería darnos una idea clara de cómo Hamcrest encaja en un método de prueba:
public class StringMatcherTest { @Test public void given2Strings_whenEqual_thenCorrect() { String a = "foo"; String b = "FOO"; assertThat(a, equalToIgnoringCase(b)); } }
En las siguientes secciones, echaremos un vistazo a otras combinaciones comunes que ofrece Hamcrest .
4. El comparador de objetos
Hamcrest proporciona comparadores para realizar afirmaciones sobre objetos Java arbitrarios.
Para afirmar que el método toString de un objeto devuelve una cadena especificada :
@Test public void givenBean_whenToStringReturnsRequiredString_thenCorrect(){ Person person=new Person("Barrack", "Washington"); String str=person.toString(); assertThat(person,hasToString(str)); }
También podemos comprobar que una clase es una subclase de otra:
@Test public void given2Classes_whenOneInheritsFromOther_thenCorrect(){ assertThat(Cat.class,typeCompatibleWith(Animal.class)); } }
5. The Bean Matcher
Podemos utilizar el comparador Bean de Hamcrest para inspeccionar las propiedades de un bean Java.
Suponga el siguiente bean Person :
public class Person { String name; String address; public Person(String personName, String personAddress) { name = personName; address = personAddress; } }
Podemos comprobar si el bean tiene la propiedad, nombre así:
@Test public void givenBean_whenHasValue_thenCorrect() { Person person = new Person("Baeldung", 25); assertThat(person, hasProperty("name")); }
También podemos verificar si Person tiene la propiedad de dirección , inicializada a Nueva York:
@Test public void givenBean_whenHasCorrectValue_thenCorrect() { Person person = new Person("Baeldung", "New York"); assertThat(person, hasProperty("address", equalTo("New York"))); }
También podemos comprobar si dos objetos Person están construidos con los mismos valores:
@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 proporciona emparejadores para inspeccionar las colecciones .
Compruebe sencillamente si una colección está vacía:
@Test public void givenCollection_whenEmpty_thenCorrect() { List emptyList = new ArrayList(); assertThat(emptyList, empty()); }
Para comprobar el tamaño de una colección:
@Test public void givenAList_whenChecksSize_thenCorrect() { List hamcrestMatchers = Arrays.asList( "collections", "beans", "text", "number"); assertThat(hamcrestMatchers, hasSize(4)); }
También podemos usarlo para afirmar que una matriz tiene un tamaño requerido:
@Test public void givenArray_whenChecksSize_thenCorrect() { String[] hamcrestMatchers = { "collections", "beans", "text", "number" }; assertThat(hamcrestMatchers, arrayWithSize(4)); }
Para comprobar si una colección contiene determinados miembros, independientemente del orden:
@Test public void givenAListAndValues_whenChecksListForGivenValues_thenCorrect() { List hamcrestMatchers = Arrays.asList( "collections", "beans", "text", "number"); assertThat(hamcrestMatchers, containsInAnyOrder("beans", "text", "collections", "number")); }
Para afirmar además que los miembros de la Colección están en orden:
@Test public void givenAListAndValues_whenChecksListForGivenValuesWithOrder_thenCorrect() { List hamcrestMatchers = Arrays.asList( "collections", "beans", "text", "number"); assertThat(hamcrestMatchers, contains("collections", "beans", "text", "number")); }
Para verificar si una matriz tiene un solo elemento dado:
@Test public void givenArrayAndValue_whenValueFoundInArray_thenCorrect() { String[] hamcrestMatchers = { "collections", "beans", "text", "number" }; assertThat(hamcrestMatchers, hasItemInArray("text")); }
También podemos usar un comparador alternativo para la misma prueba:
@Test public void givenValueAndArray_whenValueIsOneOfArrayElements_thenCorrect() { String[] hamcrestMatchers = { "collections", "beans", "text", "number" }; assertThat("text", isOneOf(hamcrestMatchers)); }
O aún podemos hacer lo mismo con un comparador diferente como este:
@Test public void givenValueAndArray_whenValueFoundInArray_thenCorrect() { String[] array = new String[] { "collections", "beans", "text", "number" }; assertThat("beans", isIn(array)); }
También podemos verificar si la matriz contiene elementos dados independientemente del orden:
@Test public void givenArrayAndValues_whenValuesFoundInArray_thenCorrect() { String[] hamcrestMatchers = { "collections", "beans", "text", "number" }; assertThat(hamcrestMatchers, arrayContainingInAnyOrder("beans", "collections", "number", "text")); }
Para verificar si la matriz contiene elementos dados pero en el orden dado:
@Test public void givenArrayAndValues_whenValuesFoundInArrayInOrder_thenCorrect() { String[] hamcrestMatchers = { "collections", "beans", "text", "number" }; assertThat(hamcrestMatchers, arrayContaining("collections", "beans", "text", "number")); }
Cuando nuestra colección es un mapa, podemos usar los siguientes comparadores en estas funciones respectivas:
Para comprobar si contiene una clave determinada:
@Test public void givenMapAndKey_whenKeyFoundInMap_thenCorrect() { Map map = new HashMap(); map.put("blogname", "baeldung"); assertThat(map, hasKey("blogname")); }
y un valor dado:
@Test public void givenMapAndValue_whenValueFoundInMap_thenCorrect() { Map map = new HashMap(); map.put("blogname", "baeldung"); assertThat(map, hasValue("baeldung")); }
y finalmente una entrada dada (clave, valor):
@Test public void givenMapAndEntry_whenEntryFoundInMap_thenCorrect() { Map map = new HashMap(); map.put("blogname", "baeldung"); assertThat(map, hasEntry("blogname", "baeldung")); }
7. El comparador de números
Los comparadores de números se utilizan para realizar afirmaciones sobre variables de la clase de números .
Para comprobar una condición mayor que:
@Test public void givenAnInteger_whenGreaterThan0_thenCorrect() { assertThat(1, greaterThan(0)); }
Para marcar una condición mayor que o igual a :
@Test public void givenAnInteger_whenGreaterThanOrEqTo5_thenCorrect() { assertThat(5, greaterThanOrEqualTo(5)); }
Para comprobar menos de la condición:
@Test public void givenAnInteger_whenLessThan0_thenCorrect() { assertThat(-1, lessThan(0)); }
Para marcar una condición menor que o igual :
@Test public void givenAnInteger_whenLessThanOrEqTo5_thenCorrect() { assertThat(-1, lessThanOrEqualTo(5)); }
Para comprobar la condición closeTo :
@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.
La implementación completa de todos estos ejemplos y fragmentos de código se puede encontrar en mi proyecto Hamcrest github.