¿Cómo afirmo que un Iterable contiene elementos con una determinada propiedad?

103

Supongamos que quiero probar un método con esta firma:

List<MyItem> getMyItems();

Supongamos que MyItemes un Pojo que tiene muchas propiedades, una de las cuales es "name"a través de getName().

Todo lo que me importa verificar es que el List<MyItem>, o cualquiera Iterable, contiene dos MyIteminstancias, cuyas "name"propiedades tienen los valores "foo"y "bar". Si alguna otra propiedad no coincide, realmente no me importa para los propósitos de esta prueba. Si los nombres coinciden, es una prueba exitosa.

Me gustaría que fuera de una sola línea si es posible. Aquí hay una "pseudo-sintaxis" del tipo de cosas que me gustaría hacer.

assert(listEntriesMatchInAnyOrder(myClass.getMyItems(), property("name"), new String[]{"foo", "bar"});

¿Hamcrest sería bueno para este tipo de cosas? Si es así, ¿cuál sería exactamente la versión de Hamcrest de mi pseudo-sintaxis anterior?

Kevin Pauli
fuente

Respuestas:

125

Gracias @Razvan, que me indicó la dirección correcta. Pude obtenerlo en una línea y busqué con éxito las importaciones de Hamcrest 1.3.

las importaciones:

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.beans.HasPropertyWithValue.hasProperty;

el código:

assertThat( myClass.getMyItems(), contains(
    hasProperty("name", is("foo")), 
    hasProperty("name", is("bar"))
));
Kevin Pauli
fuente
49

Tratar:

assertThat(myClass.getMyItems(),
                          hasItem(hasProperty("YourProperty", is("YourValue"))));
Razvan
fuente
2
solo como un nodo lateral: esta es una solución de Hamcrest (no asertj)
Hartmut P.
46

No es especialmente Hamcrest, pero creo que vale la pena mencionarlo aquí. Lo que uso con bastante frecuencia en Java8 es algo como:

assertTrue(myClass.getMyItems().stream().anyMatch(item -> "foo".equals(item.getName())));

(Editado con una ligera mejora de Rodrigo Manyari. Es un poco menos detallado. Ver comentarios).

Puede que sea un poco más difícil de leer, pero me gusta el tipo y la seguridad de la refactorización. También es genial para probar múltiples propiedades de frijoles en combinación. por ejemplo, con una expresión && similar a Java en el filtro lambda.

Mario Eis
fuente
2
Ligera mejora: assertTrue (myClass.getMyItems (). Stream (). AnyMatch (item -> "foo" .equals (item.getName ()));
Rodrigo Manyari
@RodrigoManyari, falta el paréntesis de cierre
Abdull
1
Esta solución desperdicia la posibilidad de mostrar un mensaje de error apropiado.
Giulio Caccin
@GiulioCaccin No creo que lo haga. Si usa JUnit, podría / debería usar los métodos de aserción sobrecargados y escribir asertTrue (..., "Mi propio mensaje de falla de prueba"); Ver más en junit.org/junit5/docs/current/api/org/junit/jupiter/api/…
Mario Eis
Quiero decir, si hace la afirmación contra un booleano, pierde la capacidad de imprimir automáticamente la diferencia real / esperada. Es posible afirmar usando un comparador, pero necesita modificar esta respuesta para que sea similar a otras en esta página para hacerlo.
Giulio Caccin
20

Assertj es bueno en esto.

import static org.assertj.core.api.Assertions.assertThat;

    assertThat(myClass.getMyItems()).extracting("name").contains("foo", "bar");

La gran ventaja de assertj en comparación con hamcrest es el fácil uso de la finalización del código.

Frank Neblung
fuente
16

AssertJ proporciona una característica excelente en extracting(): puede pasar mensajes de correo electrónico Functionpara extraer campos. Proporciona una comprobación en tiempo de compilación.
También puede afirmar el tamaño primero fácilmente.

Daría:

import static org.assertj.core.api.Assertions;

Assertions.assertThat(myClass.getMyItems())
          .hasSize(2)
          .extracting(MyItem::getName)
          .containsExactlyInAnyOrder("foo", "bar"); 

containsExactlyInAnyOrder() afirma que la lista contiene solo estos valores, sea cual sea el orden.

Para afirmar que la lista contiene estos valores cualquiera que sea el orden, pero también puede contener otros valores, use contains():

.contains("foo", "bar"); 

Como nota al margen: para afirmar múltiples campos de elementos de a List, con AssertJ lo hacemos envolviendo los valores esperados para cada elemento en una tuple()función:

import static org.assertj.core.api.Assertions;
import static org.assertj.core.groups.Tuple;

Assertions.assertThat(myClass.getMyItems())
          .hasSize(2)
          .extracting(MyItem::getName, MyItem::getOtherValue)
          .containsExactlyInAnyOrder(
               tuple("foo", "OtherValueFoo"),
               tuple("bar", "OtherValueBar")
           ); 
davidxxx
fuente
4
No entiendo por qué esto no tiene votos a favor. Creo que esta es la mejor respuesta, de lejos.
PeMa
1
La biblioteca assertJ es mucho más legible que la API de aserción JUnit.
Sangimed el
@Sangimed De acuerdo y también lo prefiero a Hamcrest.
davidxxx
En mi opinión, esto es un poco menos legible ya que separa el "valor real" del "valor esperado" y los coloca en un orden que debe coincidir.
Terran
5

Siempre que su List sea una clase concreta, puede simplemente llamar al método contains () siempre que haya implementado su método equals () en MyItem.

// given 
// some input ... you to complete

// when
List<MyItems> results = service.getMyItems();

// then
assertTrue(results.contains(new MyItem("foo")));
assertTrue(results.contains(new MyItem("bar")));

Supone que ha implementado un constructor que acepta los valores que desea afirmar. Me doy cuenta de que no está en una sola línea, pero es útil saber qué valor falta en lugar de verificar ambos a la vez.

Puntilla
fuente
1
Realmente me gusta su solución, pero ¿debería modificar todo ese código para una prueba?
Kevin Bowersox
Me imagino que cada respuesta aquí requerirá alguna configuración de prueba, ejecución del método para probar y luego afirmar las propiedades. No hay una sobrecarga real para mi respuesta por lo que puedo ver, solo que tengo dos afirmaciones en líneas separadas para que una afirmación fallida pueda identificar claramente qué valor falta.
Brad
Sería mejor incluir también un mensaje dentro de assertTrue para que el mensaje de error sea más inteligible. Sin un mensaje, si falla, JUnit simplemente lanzará un AssertionFailedError sin ningún mensaje de error. Así que es mejor incluir algo como "los resultados deben contener un nuevo MyItem (\" foo \ ")".
Max
Sí, tiene usted razón. Recomendaría Hamcrest en cualquier caso, y nunca uso assertTrue () estos días
Brad
En una nota al margen, su POJO o DTO debe definir el método de igualdad
Tayab Hussain
1

AssertJ 3.9.1 admite el uso de predicado directo en el anyMatchmétodo.

assertThat(collection).anyMatch(element -> element.someProperty.satisfiesSomeCondition())

Este es un caso de uso generalmente adecuado para condiciones arbitrariamente complejas.

Para condiciones simples, prefiero usar el extractingmétodo (ver arriba) porque el resultado iterable bajo prueba podría respaldar la verificación del valor con una mejor legibilidad. Ejemplo: puede proporcionar una API especializada, como el containsmétodo en la respuesta de Frank Neblung. O puede llamarlo anyMatchmás tarde de todos modos y usar una referencia de método como "searchedvalue"::equals. También se pueden poner múltiples extractores en el extractingmétodo, el resultado se verifica posteriormente usando tuple().

Tomáš Záluský
fuente