Jak potwierdzić, że iterowalna zawiera elementy o określonej właściwości?

103

Załóżmy, że chcę przetestować jednostkowo metodę z tym podpisem:

List<MyItem> getMyItems();

Załóżmy, że MyItemjest to Pojo, które ma wiele właściwości, z których jedna jest "name"dostępna przez getName().

Jedyne, co mi zależy na weryfikacji, to to List<MyItem>, czy element lub any Iterablezawiera dwa MyItemwystąpienia, których "name"właściwości mają wartości "foo"i "bar". Jeśli jakiekolwiek inne właściwości nie pasują, nie obchodzą mnie cele tego testu. Jeśli nazwy pasują do siebie, jest to udany test.

Chciałbym, żeby to było jednowierszowe, jeśli to możliwe. Oto trochę „pseudo-składni” tego rodzaju, co chciałbym zrobić.

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

Czy Hamcrest byłby dobry do tego typu rzeczy? Jeśli tak, to jaka dokładnie byłaby wersja hamcrest powyższej pseudo-składni?

Kevin Pauli
źródło

Odpowiedzi:

125

Dziękuję @Razvan, który wskazał mi właściwy kierunek. Udało mi się go zdobyć w jednej linii iz powodzeniem polowałem na import Hamcrest 1.3.

import:

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;

kod:

assertThat( myClass.getMyItems(), contains(
    hasProperty("name", is("foo")), 
    hasProperty("name", is("bar"))
));
Kevin Pauli
źródło
49

Próbować:

assertThat(myClass.getMyItems(),
                          hasItem(hasProperty("YourProperty", is("YourValue"))));
Razvan
źródło
2
tylko jako węzeł boczny - to jest rozwiązanie hamcrest (nie assertj)
Hartmut P.
46

Nie jest to szczególnie Hamcrest, ale myślę, że warto o tym tutaj wspomnieć. To, czego często używam w Javie8, to coś takiego:

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

(Zredagowano zgodnie z niewielką poprawą Rodrigo Manyariego. Jest trochę mniej szczegółowa. Zobacz komentarze).

Może to być trochę trudniejsze do odczytania, ale podoba mi się typ i bezpieczeństwo refaktoryzacji. Jest również fajny do testowania wielu właściwości fasoli w połączeniu. np. z wyrażeniem && podobnym do języka java w filtrze lambda.

Mario Eis
źródło
2
Nieznaczne ulepszenie: assertTrue (myClass.getMyItems (). Stream (). AnyMatch (item -> "foo" .equals (item.getName ()));
Rodrigo Manyari
@RodrigoManyari, brak nawiasu zamykającego
Abdull
1
Takie rozwiązanie marnuje możliwość wyświetlenia odpowiedniego komunikatu o błędzie.
Giulio Caccin
@GiulioCaccin Myślę, że tak nie jest. Jeśli używasz JUnit, możesz / powinieneś użyć przeciążonych metod asercji i napisać assertTrue (..., "Mój własny komunikat o niepowodzeniu testu"); Zobacz więcej na junit.org/junit5/docs/current/api/org/junit/jupiter/api/…
Mario Eis
To znaczy, jeśli wykonasz asercję względem wartości logicznej, stracisz możliwość automatycznego drukowania rzeczywistej / oczekiwanej różnicy. Możliwe jest potwierdzenie za pomocą dopasowania, ale aby to zrobić, musisz zmodyfikować tę odpowiedź, aby była podobna do innych na tej stronie.
Giulio Caccin
20

Assertj jest w tym dobry.

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

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

Dużym plusem assertj w porównaniu do hamcrest jest łatwość uzupełniania kodu.

Frank Neblung
źródło
16

AssertJ zapewnia doskonałą funkcję w extracting(): możesz podać Functions, aby wyodrębnić pola. Zapewnia kontrolę w czasie kompilacji.
Możesz również łatwo ustalić rozmiar jako pierwszy.

Dałoby to:

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

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

containsExactlyInAnyOrder() potwierdza, że ​​lista zawiera tylko te wartości, niezależnie od kolejności.

Aby potwierdzić, że lista zawiera te wartości niezależnie od kolejności, ale może również zawierać inne wartości, użyj contains():

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

Na marginesie: aby potwierdzić wiele pól z elementów a List, za pomocą AssertJ robimy to, opakowując oczekiwane wartości dla każdego elementu w tuple()funkcję:

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
źródło
4
Nie rozumiem, dlaczego to nie ma pozytywnych głosów. Myślę, że to zdecydowanie najlepsza odpowiedź.
PeMa
1
Biblioteka assertJ jest znacznie bardziej czytelna niż interfejs API asercji JUnit.
Sangimed
@Sangimed Zgadzam się, a także wolę to niż hamcrest.
davidxxx
Moim zdaniem jest to nieco mniej czytelne, ponieważ oddziela „rzeczywistą wartość” od „oczekiwanej wartości” i układa je w kolejności, która musi pasować.
Terran
5

Dopóki List jest klasą konkretną, możesz po prostu wywołać metodę zawiera (), o ile zaimplementowano metodę equals () w 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")));

Zakłada się, że zaimplementowano konstruktora, który akceptuje wartości, dla których chcesz potwierdzić. Zdaję sobie sprawę, że to nie jest w jednej linii, ale warto wiedzieć, której wartości brakuje, zamiast sprawdzać obie na raz.

Ćwiek
źródło
1
Naprawdę podoba mi się twoje rozwiązanie, ale czy powinien zmodyfikować cały kod do testu?
Kevin Bowersox
Wydaje mi się, że każda odpowiedź będzie wymagała konfiguracji testu, wykonania metody do przetestowania, a następnie potwierdzenia właściwości. Z tego, co widzę, nie ma żadnego rzeczywistego obciążenia związanego z moją odpowiedzią, tylko że mam dwa stwierdzenia dotyczące linii dotyczących stawki morskiej, aby nieudane potwierdzenie mogło jasno określić, jakiej wartości brakuje.
Brad
Najlepiej byłoby również uwzględnić komunikat w assertTrue, aby komunikat o błędzie był bardziej czytelny. Bez komunikatu, jeśli to się nie powiedzie, JUnit wyśle ​​po prostu AssertionFailedError bez żadnego komunikatu o błędzie. Dlatego najlepiej zawrzeć coś w rodzaju „wyniki powinny zawierać nowy element MyItem (\" foo \ ")”.
Max
Tak masz rację. W każdym razie poleciłbym Hamcrest i obecnie nigdy nie używam assertTrue ()
Brad
Na marginesie, twoje POJO lub DTO powinny definiować metodę równości
Tayab Hussain
1

AssertJ 3.9.1 obsługuje bezpośrednie użycie predykatów w anyMatchmetodzie.

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

Jest to ogólnie odpowiedni przypadek użycia dla dowolnie złożonych warunków.

W przypadku prostych warunków wolę używać extractingmetody (patrz powyżej), ponieważ wynikowe iterowalne testy mogą wspierać weryfikację wartości z lepszą czytelnością. Przykład: może zapewnić wyspecjalizowane API, takie jak containsmetoda w odpowiedzi Franka Neblunga. Lub możesz anyMatchgo później wywołać i użyć odwołania do metody, takiego jak "searchedvalue"::equals. Do extractingmetody można również wprowadzić wiele ekstraktorów , a następnie zweryfikować wynik za pomocą tuple().

Tomáš Záluský
źródło