Wie kann ich behaupten, dass eine Iterable Elemente mit einer bestimmten Eigenschaft enthält?

103

Angenommen, ich möchte eine Methode mit dieser Signatur testen:

List<MyItem> getMyItems();

Angenommen, es MyItemhandelt sich um ein Pojo mit vielen Eigenschaften, auf die "name"über zugegriffen werden kann getName().

Ich möchte nur überprüfen, ob die List<MyItem>oder eine Iterableder beiden MyItemInstanzen zwei Instanzen enthält , deren "name"Eigenschaften die Werte "foo"und haben "bar". Wenn andere Eigenschaften nicht übereinstimmen, interessieren mich die Zwecke dieses Tests nicht wirklich. Wenn die Namen übereinstimmen, ist dies ein erfolgreicher Test.

Ich möchte, dass es wenn möglich einzeilig ist. Hier ist eine "Pseudo-Syntax", wie ich sie gerne machen würde.

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

Wäre Hamcrest gut für so etwas? Wenn ja, was genau wäre die Hamcrest-Version meiner obigen Pseudosyntax?

Kevin Pauli
quelle

Antworten:

125

Vielen Dank an @Razvan, der mich in die richtige Richtung gelenkt hat. Ich konnte es in einer Zeile bekommen und habe die Importe für Hamcrest 1.3 erfolgreich gejagt.

die Importe:

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;

der Code:

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

Versuchen:

assertThat(myClass.getMyItems(),
                          hasItem(hasProperty("YourProperty", is("YourValue"))));
Razvan
quelle
2
Nur als Seitenknoten - dies ist eine Hamcrest-Lösung (nicht behauptet)
Hartmut P.
46

Es ist nicht besonders Hamcrest, aber ich denke, es ist erwähnenswert, hier zu erwähnen. Was ich in Java8 ziemlich oft benutze, ist wie folgt:

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

(Bearbeitet zu Rodrigo Manyaris leichter Verbesserung. Es ist etwas weniger ausführlich. Siehe Kommentare.)

Es ist vielleicht etwas schwieriger zu lesen, aber ich mag den Typ und die Refactoring-Sicherheit. Es ist auch cool, um mehrere Bohneneigenschaften in Kombination zu testen. zB mit einem Java-ähnlichen && Ausdruck im Filter Lambda.

Mario Eis
quelle
2
Leichte Verbesserung: assertTrue (myClass.getMyItems (). Stream (). AnyMatch (item -> "foo" .equals (item.getName ()))
Rodrigo Manyari
@ RodrigoManyari, schließende Klammer fehlt
Abdull
1
Diese Lösung verschwendet die Möglichkeit, eine entsprechende Fehlermeldung anzuzeigen.
Giulio Caccin
@ GiulioCaccin Ich glaube nicht. Wenn Sie JUnit verwenden, können / sollten Sie die überladenen Assertionsmethoden verwenden und assertTrue schreiben (..., "Meine eigene Testfehlermeldung"); Weitere Informationen finden Sie
Mario Eis
Ich meine, wenn Sie die Behauptung gegen einen Booleschen Wert machen, verlieren Sie die Fähigkeit, den tatsächlichen / erwarteten Unterschied automatisch auszudrucken. Es ist möglich, einen Matcher zu verwenden, aber Sie müssen diese Antwort so ändern, dass sie anderen auf dieser Seite ähnelt.
Giulio Caccin
20

Assertj ist gut darin.

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

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

Ein großes Plus für assertj im Vergleich zu hamcrest ist die einfache Verwendung der Code-Vervollständigung.

Frank Neblung
quelle
16

AssertJ bietet eine hervorragende Funktion in extracting(): Sie können Functions übergeben, um Felder zu extrahieren. Es bietet eine Überprüfung zur Kompilierungszeit.
Sie können die Größe auch einfach zuerst festlegen.

Es würde geben:

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

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

containsExactlyInAnyOrder() behauptet, dass die Liste unabhängig von der Reihenfolge nur diese Werte enthält.

Um zu behaupten, dass die Liste diese Werte unabhängig von der Reihenfolge enthält, aber auch andere Werte enthalten kann, verwenden Sie contains():

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

Als Randnotiz: ListUm mehrere Felder aus Elementen von a zu aktivieren, tun wir dies mit AssertJ, indem wir die erwarteten Werte für jedes Element in eine tuple()Funktion einschließen :

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
quelle
4
Verstehe nicht, warum dies keine positiven Stimmen hat. Ich denke, das ist bei weitem die beste Antwort.
PeMa
1
Die assertJ-Bibliothek ist viel besser lesbar als die JUnit-Assertion-API.
Sangimed
@ Sangimed Einverstanden und ich bevorzuge es zu hamcrest.
Davidxxx
Meiner Meinung nach ist dies etwas weniger lesbar, da der "tatsächliche Wert" vom "erwarteten Wert" getrennt und in eine Reihenfolge gebracht wird, die übereinstimmen muss.
Terraner
4

Solange Ihre Liste eine konkrete Klasse ist, können Sie einfach die Methode includes () aufrufen, solange Sie Ihre Methode equals () in MyItem implementiert haben.

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

Angenommen, Sie haben einen Konstruktor implementiert, der die Werte akzeptiert, für die Sie eine Bestätigung erstellen möchten. Mir ist klar, dass dies nicht in einer einzelnen Zeile steht, aber es ist nützlich zu wissen, welcher Wert fehlt, anstatt beide gleichzeitig zu überprüfen.

Brad
quelle
1
Ich mag deine Lösung wirklich, aber sollte er den ganzen Code für einen Test modifizieren?
Kevin Bowersox
Ich gehe davon aus, dass für jede Antwort hier ein Testaufbau, die Ausführung der zu testenden Methode und die Bestätigung der Eigenschaften erforderlich sind. Meine Antwort auf das, was ich sehen kann, hat keinen wirklichen Aufwand, nur dass ich zwei Behauptungen auf Seaprate-Linien habe, damit eine fehlgeschlagene Behauptung klar erkennen kann, welcher Wert fehlt.
Brad
Es ist am besten, auch eine Nachricht in assertTrue aufzunehmen, damit die Fehlermeldung verständlicher wird. Wenn eine Nachricht fehlschlägt, löst JUnit ohne Nachricht nur einen AssertionFailedError ohne Fehlermeldung aus. Fügen Sie am besten so etwas wie "Ergebnisse sollten neues MyItem (" foo ") enthalten" ein.
Max
Ja, du hast recht. Ich würde Hamcrest auf jeden Fall empfehlen, und ich benutze heutzutage nie assertTrue ()
Brad
Nebenbei
1

AssertJ 3.9.1 unterstützt die direkte Verwendung von Prädikaten in anyMatchMethoden.

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

Dies ist im Allgemeinen ein geeigneter Anwendungsfall für beliebig komplexe Zustände.

Für einfache Bedingungen bevorzuge ich extracting Methode (siehe oben), da die daraus resultierende iterierbare Prüfung die Wertüberprüfung mit besserer Lesbarkeit unterstützen kann. Beispiel: Es kann eine spezielle API wie die containsMethode in Frank Neblungs Antwort bereitstellen . Oder Sie können anyMatches später trotzdem aufrufen und Methodenreferenzen wie z "searchedvalue"::equals. Es können auch mehrere Extraktoren in extractingMethode gebracht werden, deren Ergebnis anschließend mit überprüft wird tuple().

Tomáš Záluský
quelle