Come si afferma che un Iterable contiene elementi con una determinata proprietà?


103

Supponiamo di voler eseguire un test unitario di un metodo con questa firma:

List<MyItem> getMyItems();

Supponiamo che MyItemsia un Pojo che ha molte proprietà, una delle quali è "name"accessibile tramite getName().

Tutto quello che mi interessa verificare è che List<MyItem>, o any Iterable, contenga due MyItemistanze, le cui "name"proprietà hanno i valori "foo"e "bar". Se altre proprietà non corrispondono, non mi interessa davvero gli scopi di questo test. Se i nomi corrispondono, è un test di successo.

Vorrei che fosse una battuta, se possibile. Ecco un po 'di "pseudo-sintassi" del genere di cose che vorrei fare.

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

Hamcrest andrebbe bene per questo tipo di cose? In tal caso, quale sarebbe esattamente la versione hamcrest della mia pseudo-sintassi sopra?

Risposte:


125

Grazie @Razvan che mi ha indicato la giusta direzione. Sono stato in grado di ottenerlo in una riga e ho cercato con successo le importazioni per Hamcrest 1.3.

le importazioni:

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;

il codice:

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

49

Provare:

assertThat(myClass.getMyItems(),
                          hasItem(hasProperty("YourProperty", is("YourValue"))));

2
proprio come un nodo laterale - questa è una soluzione hamcrest (non assertj)
Hartmut P.

46

Non è particolarmente Hamcrest, ma penso che valga la pena menzionarlo qui. Quello che uso abbastanza spesso in Java8 è qualcosa del tipo:

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

(Modificato in base al leggero miglioramento di Rodrigo Manyari. È un po 'meno prolisso. Vedi i commenti.)

Potrebbe essere un po 'più difficile da leggere, ma mi piace il tipo e la sicurezza del refactoring. È anche utile per testare più proprietà dei fagioli in combinazione. ad esempio con un'espressione && simile a java nel filtro lambda.


2
Leggero miglioramento: assertTrue (myClass.getMyItems (). Stream (). AnyMatch (item -> "foo" .equals (item.getName ()));
Rodrigo Manyari,

@RodrigoManyari, parentesi di chiusura mancante
Abdull

1
Questa soluzione spreca la possibilità di mostrare un messaggio di errore appropriato.
Giulio Caccin

@GiulioCaccin Non credo che lo sia. Se usi JUnit, potresti / dovresti usare i metodi di asserzione sovraccaricata e scrivere assertTrue (..., "My own test failure message"); Vedi di più su junit.org/junit5/docs/current/api/org/junit/jupiter/api/…
Mario Eis

Voglio dire, se fai l'asserzione contro un booleano, perdi la capacità di stampare automaticamente la differenza effettiva / attesa. È possibile affermare utilizzando un matcher, ma è necessario modificare questa risposta in modo che sia simile alle altre in questa pagina per farlo.
Giulio Caccin

20

Assertj è bravo in questo.

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

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

Un grande vantaggio per assertj rispetto a hamcrest è il facile utilizzo del completamento del codice.


16

AssertJ fornisce un'eccellente funzionalità in extracting(): puoi passare Functions per estrarre i campi. Fornisce un controllo in fase di compilazione.
Puoi anche affermare facilmente la dimensione prima.

Darebbe:

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

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

containsExactlyInAnyOrder() afferma che l'elenco contiene solo questi valori qualunque sia l'ordine.

Per affermare che l'elenco contiene questi valori qualunque sia l'ordine, ma può contenere anche altri valori, utilizzare contains():

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

Come nota a Listmargine : per affermare più campi da elementi di a , con AssertJ lo facciamo avvolgendo i valori attesi per ogni elemento in una tuple()funzione:

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")
           ); 

4
Non capire perché questo non ha voti positivi. Penso che questa sia di gran lunga la risposta migliore.
PeMa

1
La libreria assertJ è molto più leggibile dell'API di asserzione JUnit.
Sangimed

@Sangimed d'accordo e anche io lo preferisco a hamcrest.
davidxxx

A mio parere questo è leggermente meno leggibile in quanto separa il "valore effettivo" dal "valore atteso" e li mette in un ordine che deve corrispondere.
Terran

5

Finché la tua List è una classe concreta, puoi semplicemente chiamare il metodo contains () purché tu abbia implementato il tuo metodo equals () su 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")));

Si presume che tu abbia implementato un costruttore che accetta i valori su cui vuoi affermare. Mi rendo conto che questo non è su una singola riga, ma è utile sapere quale valore manca piuttosto che controllarli entrambi contemporaneamente.


1
Mi piace molto la tua soluzione, ma dovrebbe modificare tutto il codice per un test?
Kevin Bowersox

Immagino che ogni risposta qui richiederà una configurazione di test, l'esecuzione del metodo da testare e quindi affermare le proprietà. Non c'è un vero sovraccarico per la mia risposta da quello che posso vedere, solo che ho due affermazioni sulle linee Seaprate in modo che un'asserzione fallita possa identificare chiaramente quale valore manca.
Brad

Sarebbe meglio includere anche un messaggio all'interno di assertTrue in modo che il messaggio di errore sia più comprensibile. Senza un messaggio, se fallisce, JUnit lancerà semplicemente un AssertionFailedError senza alcun messaggio di errore. Quindi è meglio includere qualcosa come "i risultati dovrebbero contenere un nuovo MyItem (\" pippo \ ")".
Max

Si hai ragione. Consiglierei Hamcrest in ogni caso, e non uso mai assertTrue () in questi giorni
Brad

Per una nota a margine il tuo POJO o DTO dovrebbe definire il metodo uguale
Tayab Hussain

1

AssertJ 3.9.1 supporta l'utilizzo diretto del predicato nel anyMatchmetodo.

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

Questo è generalmente un caso d'uso adatto per condizioni arbitrariamente complesse.

Per le condizioni semplici preferisco usare il extractingmetodo (vedi sopra) perché l'iterable-under-test risultante potrebbe supportare la verifica del valore con una migliore leggibilità. Esempio: può fornire API specializzate come il containsmetodo nella risposta di Frank Neblung. Oppure puoi comunque richiamarlo anyMatchin un secondo momento e utilizzare riferimenti al metodo come "searchedvalue"::equals. Anche più estrattori possono essere inseriti nel extractingmetodo, risultato successivamente verificato utilizzando tuple().

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.