Bagaimana cara menegaskan Iterable berisi elemen dengan properti tertentu?

103

Asumsikan saya ingin menguji unit metode dengan tanda tangan ini:

List<MyItem> getMyItems();

Diasumsikan MyItemsebagai Pojo yang memiliki banyak properti, salah satunya "name"diakses melalui getName().

Yang saya pedulikan untuk memverifikasi adalah bahwa List<MyItem>, atau apapun Iterable, berisi dua MyItemcontoh, yang "name"propertinya memiliki nilai "foo"dan "bar". Jika ada properti lain yang tidak cocok, saya tidak terlalu peduli dengan tujuan pengujian ini. Jika namanya cocok, itu tes yang berhasil.

Saya ingin ini menjadi satu baris jika memungkinkan. Berikut adalah beberapa "sintaksis semu" dari hal yang ingin saya lakukan.

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

Apakah Hamcrest bagus untuk hal semacam ini? Jika demikian, apa sebenarnya versi hamcrest dari sintaks semu saya di atas?

Kevin Pauli
sumber

Jawaban:

125

Terima kasih @Razvan yang telah mengarahkan saya ke arah yang benar. Saya bisa mendapatkannya dalam satu baris dan saya berhasil memburu impor untuk Hamcrest 1.3.

impor:

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;

Kode:

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

Mencoba:

assertThat(myClass.getMyItems(),
                          hasItem(hasProperty("YourProperty", is("YourValue"))));
Razvan
sumber
2
hanya sebagai simpul samping - ini adalah solusi hamcrest (bukan assertj)
Hartmut P.
46

Ini bukan Hamcrest, tapi saya rasa perlu disebutkan di sini. Yang sering saya gunakan di Java8 adalah seperti:

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

(Diedit untuk sedikit perbaikan Rodrigo Manyari. Sedikit kurang bertele-tele. Lihat komentar.)

Ini mungkin sedikit lebih sulit untuk dibaca, tapi saya suka jenis dan keamanan refactoring. Ini juga keren untuk menguji beberapa properti kacang dalam kombinasi. misalnya dengan ekspresi && seperti java di filter lambda.

Mario Eis
sumber
2
Sedikit peningkatan: assertTrue (myClass.getMyItems (). Stream (). AnyMatch (item -> "foo" .equals (item.getName ()));
Rodrigo Manyari
@RodrigoManyari, kurung tutup hilang
Abdull
1
Solusi ini menyia-nyiakan kemungkinan untuk menampilkan pesan kesalahan yang sesuai.
Giulio Caccin
@GiulioCaccin Saya rasa tidak. Jika Anda menggunakan JUnit, Anda dapat / harus menggunakan metode pernyataan yang kelebihan beban dan menulis assertTrue (..., "Pesan kegagalan pengujian saya sendiri"); Lihat lebih lanjut di junit.org/junit5/docs/current/api/org/junit/jupiter/api/…
Mario Eis
Maksud saya, jika Anda melakukan penegasan terhadap Boolean, Anda kehilangan kemampuan untuk mencetak secara otomatis perbedaan aktual / yang diharapkan. Dimungkinkan untuk menegaskan menggunakan matcher, tetapi Anda perlu mengubah tanggapan ini agar serupa dengan yang lain di halaman ini untuk melakukannya.
Giulio Caccin
20

Assertj pandai dalam hal ini.

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

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

Nilai tambah yang besar untuk assertj dibandingkan dengan hamcrest adalah penggunaan penyelesaian kode yang mudah.

Frank Neblung
sumber
16

AssertJ menyediakan fitur yang sangat baik dalam extracting(): Anda dapat memberikan Functions untuk mengekstrak bidang. Ini memberikan cek pada waktu kompilasi.
Anda juga bisa menegaskan ukurannya terlebih dahulu dengan mudah.

Itu akan memberi:

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

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

containsExactlyInAnyOrder() menegaskan bahwa daftar hanya berisi nilai-nilai ini apa pun urutannya.

Untuk menegaskan bahwa daftar berisi nilai-nilai ini apa pun urutannya tetapi mungkin juga berisi nilai-nilai lain gunakan contains():

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

Sebagai catatan tambahan: untuk menegaskan beberapa bidang dari elemen a List, dengan AssertJ kami melakukannya dengan membungkus nilai yang diharapkan untuk setiap elemen ke dalam tuple()fungsi:

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
sumber
4
Jangan mengerti mengapa ini tidak memiliki suara positif. Saya pikir, sejauh ini, inilah jawaban terbaik.
PeMa
1
Library assertJ jauh lebih mudah dibaca dibandingkan JUnit assertion API.
Sangimed
@Sangimed Setuju dan juga saya lebih suka hamcrest.
davidxxx
Menurut pendapat saya, ini sedikit kurang dapat dibaca karena memisahkan "nilai aktual" dari "nilai yang diharapkan" dan menempatkannya dalam urutan yang harus sesuai.
Terran
5

Selama List Anda adalah kelas konkret, Anda cukup memanggil metode contains () selama Anda telah mengimplementasikan metode equals () pada 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")));

Mengasumsikan Anda telah menerapkan konstruktor yang menerima nilai yang ingin Anda tegaskan. Saya menyadari ini tidak dalam satu baris, tetapi berguna untuk mengetahui nilai mana yang hilang daripada memeriksa keduanya sekaligus.

Brad
sumber
1
Saya sangat menyukai solusi Anda, tetapi haruskah dia memodifikasi semua kode itu untuk ujian?
Kevin Bowersox
Saya membayangkan bahwa setiap jawaban di sini akan memerlukan beberapa pengaturan pengujian, eksekusi metode untuk menguji, dan kemudian menegaskan properti. Tidak ada overhead nyata untuk jawaban saya dari apa yang bisa saya lihat, hanya saja saya memiliki dua pernyataan pada garis seaprate sehingga pernyataan yang gagal dapat dengan jelas mengidentifikasi nilai apa yang hilang.
Brad
Sebaiknya juga menyertakan pesan dalam assertTrue sehingga pesan kesalahan lebih dapat dipahami. Tanpa pesan, jika gagal, JUnit hanya akan menampilkan AssertionFailedError tanpa pesan kesalahan. Jadi yang terbaik adalah menyertakan sesuatu seperti "hasil harus berisi MyItem baru (\" foo \ ")".
Maksimal
Ya kamu benar. Saya akan merekomendasikan Hamcrest dalam hal apa pun, dan saya tidak pernah menggunakan assertTrue () akhir-akhir ini
Brad
Sebagai catatan, POJO atau DTO Anda harus menentukan metode yang sama
Tayab Hussain
1

AssertJ 3.9.1 mendukung penggunaan predikat langsung dalam anyMatchmetode.

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

Ini umumnya kasus penggunaan yang sesuai untuk kondisi kompleks sewenang-wenang.

Untuk kondisi sederhana, saya lebih suka menggunakan extractingmetode (lihat di atas) karena hasil iterable-under-test mungkin mendukung verifikasi nilai dengan keterbacaan yang lebih baik. Contoh: dapat menyediakan API khusus seperti containsmetode dalam jawaban Frank Neblung. Atau Anda dapat memanggilnya anyMatchnanti dan menggunakan referensi metode seperti "searchedvalue"::equals. Juga beberapa ekstraktor dapat dimasukkan ke dalam extractingmetode, hasilnya kemudian diverifikasi menggunakan tuple().

Tomáš Záluský
sumber