Bagaimana Anda menyatakan bahwa pengecualian tertentu dilemparkan dalam tes JUnit 4?

2001

Bagaimana saya bisa menggunakan JUnit4 secara idiomatis untuk menguji bahwa beberapa kode melempar pengecualian?

Sementara saya pasti bisa melakukan sesuatu seperti ini:

@Test
public void testFooThrowsIndexOutOfBoundsException() {
  boolean thrown = false;

  try {
    foo.doStuff();
  } catch (IndexOutOfBoundsException e) {
    thrown = true;
  }

  assertTrue(thrown);
}

Saya ingat bahwa ada anotasi atau Assert.xyz atau sesuatu yang jauh lebih tidak suram dan jauh lebih dalam semangat JUnit untuk situasi semacam ini.

SCdF
sumber
21
Masalah dengan pendekatan lain tetapi ini adalah bahwa mereka selalu mengakhiri tes setelah pengecualian dilemparkan. Saya, di sisi lain, sering masih ingin memanggil org.mockito.Mockito.verifydengan berbagai parameter untuk memastikan bahwa hal-hal tertentu terjadi (sehingga layanan logger dipanggil dengan parameter yang benar) sebelum pengecualian dilemparkan.
ZeroOne
5
Anda dapat melihat bagaimana pengecualian tes di halaman wiki JUnit github.com/junit-team/junit/wiki/Exception-testing
PhoneixS
6
@Eroero - Untuk itu saya akan memiliki dua tes yang berbeda - satu untuk pengecualian dan satu untuk memverifikasi interaksi dengan tiruan Anda.
tddmonkey
Ada cara untuk melakukan ini dengan JUnit 5, saya telah memperbarui jawaban saya di bawah ini.
Dilini Rajapaksha

Jawaban:

2363

Itu tergantung pada versi JUnit dan perpustakaan apa yang Anda gunakan.

Jawaban asli untuk JUnit <= 4.12adalah:

@Test(expected = IndexOutOfBoundsException.class)
public void testIndexOutOfBoundsException() {

    ArrayList emptyList = new ArrayList();
    Object o = emptyList.get(0);

}

Meskipun jawaban https://stackoverflow.com/a/31826781/2986984 memiliki lebih banyak opsi untuk JUnit <= 4.12.

Referensi:

skaffman
sumber
66
Potongan kode ini tidak akan berfungsi jika Anda mengharapkan pengecualian hanya di suatu tempat dalam kode Anda, dan bukan selimut seperti ini.
Oh Chin Boon
4
@skaffman Ini tidak akan berfungsi dengan org.junit.experimental.theories.Theory dijalankan oleh org.junit.experimental.theories.Theories
Artem Oboturov
74
Roy Osherove mengecilkan jenis pengujian Pengecualian ini dalam Seni Pengujian Unit , karena Pengecualian mungkin ada di mana saja di dalam pengujian dan tidak hanya di dalam unit yang diuji.
Kevin Wittek
21
Saya tidak setuju dengan @ Kiview / Roy Osherove. Dalam pandangan saya, tes harus untuk perilaku, bukan implementasi. Dengan menguji bahwa metode tertentu dapat menimbulkan kesalahan, Anda mengikat tes Anda langsung ke implementasi. Saya berpendapat bahwa pengujian dalam metode yang ditunjukkan di atas memberikan tes yang lebih berharga. Peringatan yang akan saya tambahkan adalah bahwa dalam kasus ini saya akan menguji pengecualian kustom, sehingga saya tahu saya mendapatkan pengecualian yang saya inginkan.
pemegang gelar
6
Tidak juga. Saya ingin menguji perilaku kelas. Yang penting, adalah bahwa jika saya mencoba mengambil sesuatu yang tidak ada di sana, saya mendapat pengecualian. Fakta bahwa struktur data ArrayListmerespons get()adalah tidak relevan. Jika saya memilih di masa depan untuk pindah ke array primitif, maka saya harus mengubah implementasi tes ini. Struktur data harus disembunyikan, sehingga tes dapat fokus pada perilaku kelas .
nickbdyer
1317

Sunting: Sekarang JUnit 5 dan JUnit 4.13 telah dirilis, opsi terbaik adalah menggunakan Assertions.assertThrows() (untuk JUnit 5) dan Assert.assertThrows()(untuk JUnit 4.13). Lihat jawaban saya yang lain untuk detailnya.

Jika Anda belum bermigrasi ke JUnit 5, tetapi dapat menggunakan JUnit 4.7, Anda dapat menggunakan ExpectedExceptionAturan:

public class FooTest {
  @Rule
  public final ExpectedException exception = ExpectedException.none();

  @Test
  public void doStuffThrowsIndexOutOfBoundsException() {
    Foo foo = new Foo();

    exception.expect(IndexOutOfBoundsException.class);
    foo.doStuff();
  }
}

Ini jauh lebih baik daripada @Test(expected=IndexOutOfBoundsException.class)karena tes akan gagal jika IndexOutOfBoundsExceptiondilemparkan sebelumnyafoo.doStuff()

Lihat artikel ini untuk detailnya

NamshubWriter
sumber
14
@skaffman - Jika saya mengerti ini dengan benar, sepertinya exception.expect diterapkan hanya dalam satu tes, bukan seluruh kelas.
bacar
5
Jika pengecualian yang kita harapkan untuk dibuang adalah pengecualian yang diperiksa, haruskah kita menambahkan lemparan atau mencoba-menangkap atau menguji situasi ini dengan cara lain?
Mohammad Jafar Mashhadi
5
@ MartinTrummer Tidak ada kode yang harus dijalankan setelah foo.doStuff () karena pengecualian dilemparkan dan metode ini keluar. Memiliki kode setelah pengecualian yang diharapkan (dengan pengecualian menutup sumber daya pada akhirnya) tidak membantu karena seharusnya tidak pernah dieksekusi jika pengecualian dilemparkan.
Jason Thompson
9
Ini adalah pendekatan terbaik. Ada dua keuntungan di sini, dibandingkan dengan solusi skaffman. Pertama, ExpectedExceptionkelas memiliki cara untuk mencocokkan pesan pengecualian, atau bahkan menulis pencocokan Anda sendiri yang tergantung pada kelas pengecualian. Kedua, Anda dapat mengatur ekspektasi Anda segera sebelum baris kode yang Anda harapkan untuk melemparkan pengecualian - yang berarti pengujian Anda akan gagal jika baris kode yang salah melemparkan pengecualian; sedangkan tidak ada cara untuk melakukan itu dengan solusi skaffman.
Dawood ibn Kareem
5
@ MJafarMash jika pengecualian yang Anda harapkan untuk dibuang dicentang, maka Anda akan menambahkan pengecualian itu ke klausa lemparan dari metode pengujian. Anda melakukan hal yang sama setiap kali Anda menguji suatu metode yang dinyatakan untuk melemparkan pengecualian yang diperiksa, bahkan jika pengecualian tidak dipicu dalam kasus uji tertentu.
NamshubWriter
472

Hati-hati menggunakan pengecualian yang diharapkan, karena hanya menegaskan bahwa metode melempar pengecualian itu, bukan baris kode tertentu dalam pengujian.

Saya cenderung menggunakan ini untuk menguji validasi parameter, karena metode seperti itu biasanya sangat sederhana, tetapi tes yang lebih kompleks mungkin lebih baik dilayani dengan:

try {
    methodThatShouldThrow();
    fail( "My method didn't throw when I expected it to" );
} catch (MyException expectedException) {
}

Terapkan penilaian.

daveb
sumber
95
Mungkin saya sudah tua tapi saya masih lebih suka ini. Itu juga memberi saya tempat untuk menguji pengecualian itu sendiri: kadang-kadang saya memiliki pengecualian dengan getter untuk nilai-nilai tertentu, atau saya mungkin hanya mencari nilai tertentu dalam pesan (misalnya mencari "xyz" dalam pesan "kode tidak dikenal 'xyz' ").
Rodney Gitzel
3
Saya pikir pendekatan NamshubWriter memberi Anda yang terbaik dari kedua dunia.
Eddie
4
Menggunakan ExpectedException Anda dapat memanggil N exception.expect per metode untuk menguji seperti exception.expect ini (IndexOutOfBoundsException.class); foo.doStuff1 (); exception.expect (IndexOutOfBoundsException.class); foo.doStuff2 (); exception.expect (IndexOutOfBoundsException.class); foo.doStuff3 ();
user1154664
10
@ user1154664 Sebenarnya, Anda tidak bisa. Menggunakan ExpectedException Anda hanya dapat menguji bahwa satu metode melempar pengecualian, karena ketika metode itu dipanggil, tes akan berhenti dijalankan karena melemparkan pengecualian yang diharapkan!
NamshubWriter
2
Kalimat pertama Anda tidak benar. Saat menggunakan ExpectedException, hal normal yang harus dilakukan adalah mengatur ekspektasi tepat sebelum garis yang Anda harapkan untuk melempar pengecualian. Dengan begitu, jika baris sebelumnya melempar pengecualian, itu tidak akan memicu aturan, dan tes akan gagal.
Dawood ibn Kareem
213

Seperti yang dijawab sebelumnya, ada banyak cara untuk berurusan dengan pengecualian di JUnit. Tetapi dengan Java 8 ada satu lagi: menggunakan Ekspresi Lambda. Dengan Lambda Expressions, kami dapat mencapai sintaksis seperti ini:

@Test
public void verifiesTypeAndMessage() {
    assertThrown(new DummyService()::someMethod)
            .isInstanceOf(RuntimeException.class)
            .hasMessage("Runtime exception occurred")
            .hasMessageStartingWith("Runtime")
            .hasMessageEndingWith("occurred")
            .hasMessageContaining("exception")
            .hasNoCause();
}

assertThrown menerima antarmuka fungsional, yang instansinya dapat dibuat dengan ekspresi lambda, referensi metode, atau referensi konstruktor. assertThrown menerima bahwa antarmuka akan mengharapkan dan siap untuk menangani pengecualian.

Ini adalah teknik yang relatif sederhana namun kuat.

Lihatlah posting blog ini yang menjelaskan teknik ini: http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html

Kode sumber dapat ditemukan di sini: https://github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8

Pengungkapan: Saya adalah penulis blog dan proyek.

Rafal Borowiec
sumber
2
Saya suka solusi ini tetapi dapatkah saya mengunduh ini dari pakar repo?
Selwyn
@Airduster salah satu implementasi dari ide ini yang tersedia di Maven adalah stefanbirkner.github.io/vallado
NamshubWriter
6
@CristianoFontes versi yang lebih sederhana dari API ini dijadwalkan untuk JUnit 4.13. Lihat github.com/junit-team/junit/commit/…
NamshubWriter
@RafalBorowiec secara teknis, new DummyService()::someMethodadalah a MethodHandle, tetapi pendekatan ini bekerja sama baiknya dengan ekspresi lambda.
Andy
@NamshubWriter, tampaknya Junit 4.13 ditinggalkan demi Junit 5: stackoverflow.com/questions/156503/…
Vadzim
154

di junit, ada empat cara untuk menguji pengecualian.

junit5.x

  • untuk junit5.x, Anda dapat menggunakan assertThrowssebagai berikut

    @Test
    public void testFooThrowsIndexOutOfBoundsException() {
        Throwable exception = assertThrows(IndexOutOfBoundsException.class, () -> foo.doStuff());
        assertEquals("expected messages", exception.getMessage());
    }

junit4.x

  • untuk junit4.x, gunakan atribut opsional 'diharapkan' dari Test annonation

    @Test(expected = IndexOutOfBoundsException.class)
    public void testFooThrowsIndexOutOfBoundsException() {
        foo.doStuff();
    }
  • untuk junit4.x, gunakan aturan ExpectedException

    public class XxxTest {
        @Rule
        public ExpectedException thrown = ExpectedException.none();
    
        @Test
        public void testFooThrowsIndexOutOfBoundsException() {
            thrown.expect(IndexOutOfBoundsException.class)
            //you can test the exception message like
            thrown.expectMessage("expected messages");
            foo.doStuff();
        }
    }
  • Anda juga dapat menggunakan cara coba / tangkap klasik yang banyak digunakan dalam kerangka kerja junit 3

    @Test
    public void testFooThrowsIndexOutOfBoundsException() {
        try {
            foo.doStuff();
            fail("expected exception was not occured.");
        } catch(IndexOutOfBoundsException e) {
            //if execution reaches here, 
            //it indicates this exception was occured.
            //so we need not handle it.
        }
    }
  • begitu

    • jika Anda suka junit 5, maka Anda harus menyukai yang pertama
    • cara 2 digunakan ketika Anda hanya ingin menguji jenis pengecualian
    • dua yang pertama dan terakhir digunakan ketika Anda ingin menguji pesan pengecualian lebih lanjut
    • jika Anda menggunakan junit 3, maka yang ke 4 lebih disukai
  • untuk info lebih lanjut, Anda dapat membaca dokumen ini dan panduan pengguna junit5 untuk detailnya.

walsh
sumber
6
Bagi saya ini adalah jawaban terbaik, itu mencakup semua cara dengan sangat jelas, terima kasih! Secara pribadi saya terus menggunakan opsi ke-3 bahkan dengan Junit4 untuk keterbacaan, untuk menghindari blok tangkapan kosong Anda juga dapat menangkap jenis e
Nicolas Cornette yang
Apakah mungkin menggunakan ExpectedException untuk mengharapkan pengecualian yang diperiksa?
miuser
Semua itu adalah akumulasi dari tiga jawaban teratas. IMO, jawaban ini seharusnya tidak pernah diposting jika tidak menambahkan sesuatu yang baru. Hanya menjawab (pertanyaan populer) untuk perwakilan. Cukup tidak berguna.
Paul Samsotha
tentu saja, karena Anda dapat meneruskan jenis apa pun yang berasal dari Trowableke metode ExpectedException.expect. silakan lihat tanda tangan itu . @miuser
walsh
116

tl; dr

  • post-JDK8: Gunakan AssertJ atau custom lambdas untuk menegaskan perilaku yang luar biasa .

  • pra-JDK8: Saya akan merekomendasikan barang lama yang baik try- catchblok. ( Jangan lupa untuk menambahkan fail()pernyataan sebelum catchblok )

Terlepas dari Junit 4 atau JUnit 5.

cerita panjang

Dimungkinkan untuk menulis sendiri, lakukan sendiri try - catchblokir atau gunakan alat JUnit ( @Test(expected = ...)atau @Rule ExpectedExceptionfitur aturan JUnit).

Tetapi cara-cara ini tidak begitu elegan dan tidak bisa dibaca dengan baik dengan alat lain. Selain itu, perkakas JUnit memang memiliki beberapa jebakan.

  1. The try- catchblok Anda harus menulis blok di sekitar perilaku diuji dan menulis pernyataan di blok catch, yang mungkin baik-baik saja tetapi banyak menemukan bahwa gaya ini interupsi aliran pembacaan tes. Juga, Anda perlu menulis Assert.faildi akhir tryblok. Jika tidak, tes dapat kehilangan satu sisi dari pernyataan; PMD , findbugs atau Sonar akan menemukan masalah seperti itu.

  2. The @Test(expected = ...)fitur menarik seperti yang Anda dapat menulis kode kurang dan kemudian menulis tes ini konon kurang rentan terhadap kesalahan coding. Tetapi pendekatan ini kurang di beberapa daerah.

    • Jika tes perlu memeriksa hal-hal tambahan pada pengecualian seperti penyebab atau pesan (pesan pengecualian yang baik sangat penting, memiliki jenis pengecualian yang tepat mungkin tidak cukup).
    • Juga karena harapan ditempatkan di sekitar dalam metode, tergantung pada bagaimana kode yang diuji ditulis maka bagian yang salah dari kode pengujian dapat membuang pengecualian, yang mengarah ke tes positif palsu dan saya tidak yakin bahwa PMD , findbugs atau Sonar akan memberikan petunjuk tentang kode tersebut.

      @Test(expected = WantedException.class)
      public void call2_should_throw_a_WantedException__not_call1() {
          // init tested
          tested.call1(); // may throw a WantedException
      
          // call to be actually tested
          tested.call2(); // the call that is supposed to raise an exception
      }
  3. The ExpectedExceptionAturan ini juga merupakan upaya untuk memperbaiki peringatan sebelumnya, tapi rasanya sedikit aneh untuk digunakan karena menggunakan gaya harapan, EasyMock pengguna tahu betul gaya ini. Mungkin cocok untuk beberapa orang, tetapi jika Anda mengikuti prinsip-prinsip Behavior Driven Development (BDD) atau Arrange Act Assert (AAA) ExpectedExceptionaturan tidak akan cocok dengan gaya penulisan mereka. Selain itu mungkin mengalami masalah yang sama seperti @Testcara, tergantung di mana Anda menempatkan harapan.

    @Rule ExpectedException thrown = ExpectedException.none()
    
    @Test
    public void call2_should_throw_a_WantedException__not_call1() {
        // expectations
        thrown.expect(WantedException.class);
        thrown.expectMessage("boom");
    
        // init tested
        tested.call1(); // may throw a WantedException
    
        // call to be actually tested
        tested.call2(); // the call that is supposed to raise an exception
    }

    Bahkan pengecualian yang diharapkan ditempatkan sebelum pernyataan tes, itu merusak aliran bacaan Anda jika tes mengikuti BDD atau AAA.

    Juga, lihat masalah komentar ini pada JUnit dari penulis ExpectedException. JUnit 4.13-beta-2 bahkan mencela mekanisme ini:

    Tarik permintaan # 1519 : Turunkan ExpectedException

    Metode Assert.assertThrows menyediakan cara yang lebih baik untuk memverifikasi pengecualian. Selain itu, penggunaan ExpectedException rentan kesalahan saat digunakan dengan aturan lain seperti TestWatcher karena urutan aturan penting dalam kasus itu.

Jadi opsi di atas memiliki semua muatan peringatan, dan jelas tidak kebal terhadap kesalahan pengkode.

  1. Ada sebuah proyek yang saya sadari setelah membuat jawaban ini yang tampak menjanjikan, itu pengecualian .

    Seperti yang dijelaskan dalam deskripsi proyek, itu memungkinkan seorang pembuat kode menulis dalam garis fasih kode menangkap pengecualian dan menawarkan pengecualian ini untuk pernyataan terakhir. Dan Anda dapat menggunakan perpustakaan pernyataan apa pun seperti Hamcrest atau AssertJ .

    Contoh cepat diambil dari beranda:

    // given: an empty list
    List myList = new ArrayList();
    
    // when: we try to get the first element of the list
    when(myList).get(1);
    
    // then: we expect an IndexOutOfBoundsException
    then(caughtException())
            .isInstanceOf(IndexOutOfBoundsException.class)
            .hasMessage("Index: 1, Size: 0") 
            .hasNoCause();

    Seperti yang Anda lihat kode ini benar-benar mudah, Anda menangkap pengecualian pada baris tertentu, thenAPI adalah alias yang akan menggunakan AssertJ API (mirip dengan menggunakan assertThat(ex).hasNoCause()...). Pada titik tertentu proyek ini mengandalkan FEST-Assert, leluhur AssertJ . EDIT: Tampaknya proyek ini sedang menyedot dukungan Java 8 Lambdas.

    Saat ini, perpustakaan ini memiliki dua kekurangan:

    • Pada saat penulisan ini, perlu dicatat untuk mengatakan perpustakaan ini didasarkan pada Mockito 1.x karena menciptakan tiruan dari objek yang diuji di belakang layar. Karena Mockito masih belum diperbarui perpustakaan ini tidak dapat bekerja dengan kelas akhir atau metode akhir . Dan bahkan jika itu didasarkan pada Mockito 2 dalam versi saat ini, ini akan perlu untuk menyatakan pembuat mock global ( inline-mock-maker), sesuatu yang mungkin tidak seperti yang Anda inginkan, karena pembuat tiruan ini memiliki kelemahan berbeda dari pembuat tiruan biasa.

    • Ini membutuhkan lagi ketergantungan tes.

    Masalah ini tidak akan berlaku setelah perpustakaan mendukung lambdas. Namun, fungsionalitas akan diduplikasi oleh toolset AssertJ.

    Mempertimbangkan semua jika Anda tidak ingin menggunakan alat catch-exception, saya akan merekomendasikan cara lama yang baik try- catchblok, setidaknya hingga JDK7. Dan untuk pengguna JDK 8 Anda mungkin lebih suka menggunakan AssertJ karena menawarkan mungkin lebih dari sekadar menegaskan pengecualian.

  2. Dengan JDK8, lambdas memasuki panggung tes, dan mereka telah terbukti menjadi cara yang menarik untuk menegaskan perilaku yang luar biasa. AssertJ telah diperbarui untuk menyediakan API lancar yang bagus untuk menegaskan perilaku yang luar biasa.

    Dan tes sampel dengan AssertJ :

    @Test
    public void test_exception_approach_1() {
        ...
        assertThatExceptionOfType(IOException.class)
                .isThrownBy(() -> someBadIOOperation())
                .withMessage("boom!"); 
    }
    
    @Test
    public void test_exception_approach_2() {
        ...
        assertThatThrownBy(() -> someBadIOOperation())
                .isInstanceOf(Exception.class)
                .hasMessageContaining("boom");
    }
    
    @Test
    public void test_exception_approach_3() {
        ...
        // when
        Throwable thrown = catchThrowable(() -> someBadIOOperation());
    
        // then
        assertThat(thrown).isInstanceOf(Exception.class)
                          .hasMessageContaining("boom");
    }
  3. Dengan penulisan ulang hampir lengkap dari JUnit 5, pernyataan telah sedikit ditingkatkan , mereka mungkin terbukti menarik sebagai cara di luar kotak untuk menyatakan pengecualian dengan benar. Tapi sungguh-sungguh API pernyataan masih sedikit miskin, tidak ada di luar assertThrows.

    @Test
    @DisplayName("throws EmptyStackException when peeked")
    void throwsExceptionWhenPeeked() {
        Throwable t = assertThrows(EmptyStackException.class, () -> stack.peek());
    
        Assertions.assertEquals("...", t.getMessage());
    }

    Seperti yang Anda perhatikan assertEqualsmasih kembali void, dan karena itu tidak memungkinkan pernyataan rantai seperti AssertJ.

    Juga jika Anda ingat nama bentrokan dengan Matcheratau Assert, bersiaplah untuk bertemu dengan bentrokan yang sama Assertions.

Saya ingin menyimpulkan bahwa hari ini (2017-03-03) AssertJ 's kemudahan penggunaan, API yang dapat ditemukan, laju pengembangan yang cepat dan sebagai ketergantungan uji de facto adalah solusi terbaik dengan JDK8 terlepas dari kerangka pengujian (JUnit atau tidak), JDK sebelumnya seharusnya bergantung pada try-catch blok bahkan jika mereka merasa kikuk.

Jawaban ini telah disalin dari pertanyaan lain yang tidak memiliki visibilitas yang sama, saya adalah penulis yang sama.

Brice
sumber
1
Menambahkan org.junit.jupiter: junit-jupiter-engine: dependensi 5.0.0-RC2 (selain junit yang sudah ada: junit: 4.12) untuk dapat menggunakan assertThrows mungkin bukan solusi yang disukai, tetapi tidak menyebabkan solusi apa pun. masalah bagi saya.
sekitar
Saya penggemar menggunakan aturan ExpectedException tetapi selalu mengganggu saya bahwa itu rusak dengan AAA. Anda telah menulis artikel yang bagus untuk menggambarkan semua pendekatan yang berbeda dan Anda pasti mendorong saya untuk mencoba AssertJ :-) Terima kasih!
Pim Hazebroek
@PimHazebroek terima kasih. API AssertJ cukup kaya. Lebih baik menurut saya bahwa apa yang diusulkan JUnit di luar kotak.
Brice
64

Sekarang JUnit 5 dan JUnit 4.13 telah dirilis, opsi terbaik adalah menggunakan Assertions.assertThrows() (untuk JUnit 5) dan Assert.assertThrows()(untuk JUnit 4.13). Lihat Panduan Pengguna Junit 5 .

Berikut adalah contoh yang memverifikasi pengecualian dilempar, dan menggunakan Kebenaran untuk membuat pernyataan tegas pada pesan pengecualian:

public class FooTest {
  @Test
  public void doStuffThrowsIndexOutOfBoundsException() {
    Foo foo = new Foo();

    IndexOutOfBoundsException e = assertThrows(
        IndexOutOfBoundsException.class, foo::doStuff);

    assertThat(e).hasMessageThat().contains("woops!");
  }
}

Keuntungan dari pendekatan dalam jawaban lain adalah:

  1. Dibangun dalam JUnit
  2. Anda mendapatkan pesan pengecualian yang berguna jika kode di lambda tidak melempar pengecualian, dan stacktrace jika melemparkan pengecualian yang berbeda
  3. Ringkas
  4. Mengizinkan tes Anda mengikuti Arrange-Act-Assert
  5. Anda dapat secara tepat menunjukkan kode apa yang Anda harapkan untuk melempar pengecualian
  6. Anda tidak perlu mencantumkan pengecualian yang diharapkan dalam throwsklausa
  7. Anda dapat menggunakan kerangka pernyataan pilihan Anda untuk membuat pernyataan tentang pengecualian yang tertangkap

Metode serupa akan ditambahkan ke org.junit Assertdalam JUnit 4.13.

NamshubWriter
sumber
Pendekatan ini bersih, tetapi saya tidak melihat bagaimana ini memungkinkan pengujian kami untuk mengikuti "Arrange-Act-Assert", karena kita harus membungkus bagian "Act" dalam "assertThrow", yang merupakan penegasan.
Clockwork
@ Locklock The lambda adalah "akting". Tujuan Arrange-Act-Assert adalah untuk membuat kode bersih dan sederhana (dan karenanya mudah dipahami dan dipelihara). Seperti yang Anda nyatakan, pendekatan ini bersih.
NamshubWriter
Saya masih berharap saya bisa menegaskan lemparan dan pengecualian pada akhir tes, di bagian "menegaskan". Dalam pendekatan ini, Anda perlu membungkus tindakan dalam pernyataan pertama untuk menangkapnya terlebih dahulu.
Clockwork
Itu akan membutuhkan lebih banyak kode dalam setiap tes untuk melakukan penegasan. Itu lebih banyak kode dan rawan kesalahan.
NamshubWriter
43

Bagaimana dengan ini: Tangkap pengecualian yang sangat umum, pastikan itu keluar dari blok tangkap, kemudian tegaskan bahwa kelas pengecualian adalah apa yang Anda harapkan. Penegasan ini akan gagal jika a) pengecualiannya dari tipe yang salah (mis. Jika Anda mendapat Null Pointer sebagai gantinya) dan b) pengecualian tidak pernah dilempar.

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

  try {
    foo.doStuff();
  } catch (Throwable ex) {
    e = ex;
  }

  assertTrue(e instanceof IndexOutOfBoundsException);
}
Johan
sumber
3
Juga, Anda tidak akan melihat jenis Pengecualian apa yang ada dalam hasil tes ketika hari itu tiba ketika tes gagal.
jontejj
Ini dapat sedikit ditingkatkan dengan mengubah cara Anda menegaskan di akhir. assertEquals(ExpectedException.class, e.getClass())akan menunjukkan kepada Anda nilai yang diharapkan dan aktual ketika tes gagal.
Cypher
37

Solusi Gaya BDD : JUnit 4 + Catch Exception + AssertJ

import static com.googlecode.catchexception.apis.BDDCatchException.*;

@Test
public void testFooThrowsIndexOutOfBoundsException() {

    when(() -> foo.doStuff());

    then(caughtException()).isInstanceOf(IndexOutOfBoundsException.class);

}

Ketergantungan

eu.codearte.catch-exception:catch-exception:2.0
MariuszS
sumber
36

Menggunakan pernyataan AssertJ , yang dapat digunakan bersama JUnit:

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

@Test
public void testFooThrowsIndexOutOfBoundsException() {
  Foo foo = new Foo();

  assertThatThrownBy(() -> foo.doStuff())
        .isInstanceOf(IndexOutOfBoundsException.class);
}

Ini lebih baik daripada @Test(expected=IndexOutOfBoundsException.class)karena itu menjamin garis yang diharapkan dalam tes melemparkan pengecualian dan memungkinkan Anda memeriksa detail lebih lanjut tentang pengecualian, seperti pesan, lebih mudah:

assertThatThrownBy(() ->
       {
         throw new Exception("boom!");
       })
    .isInstanceOf(Exception.class)
    .hasMessageContaining("boom");

Instruksi Maven / Gradle di sini.

Weston
sumber
cara yang paling ringkas dan tidak ada yang menghargainya, aneh .. Saya hanya punya satu masalah dengan perpustakaan menegaskan, menegaskan bahwa konflik nama-bijaksana dengan junit. lebih lanjut tentang assertJ throwby: JUnit
ycomp
@comp Yah, ini adalah jawaban baru untuk pertanyaan yang sangat lama, sehingga perbedaan skor menipu.
barat
Itu mungkin solusi terbaik jika seseorang dapat menggunakan Java 8 dan AssertJ!
Pierre Henry
@comp Saya menduga konflik nama ini mungkin disebabkan oleh desain: perpustakaan AssertJ karena itu sangat menyarankan Anda untuk tidak menggunakan JUnit assertThat, selalu yang AssertJ. Juga metode JUnit mengembalikan hanya tipe "biasa", sedangkan metode AssertJ mengembalikan AbstractAssertsubclass ... memungkinkan merangkai metode seperti di atas (atau apa pun istilah teknisnya untuk ini ...).
mike rodent
@weston sebenarnya saya baru saja menggunakan teknik Anda di AssertJ 2.0.0. Tidak ada alasan untuk tidak meningkatkan, tidak diragukan lagi, tetapi meskipun Anda mungkin ingin tahu.
mike rodent
33

Untuk mengatasi masalah yang sama, saya membuat proyek kecil: http://code.google.com/p/catch-exception/

Dengan bantuan kecil ini, Anda akan menulis

verifyException(foo, IndexOutOfBoundsException.class).doStuff();

Ini kurang verbose dari aturan ExpectedException pada JUnit 4.7. Dibandingkan dengan solusi yang disediakan oleh skaffman, Anda dapat menentukan di baris kode mana Anda mengharapkan pengecualian. Saya harap ini membantu.

rwitzel
sumber
Saya berpikir untuk melakukan sesuatu seperti ini juga, tetapi akhirnya menemukan bahwa kekuatan sebenarnya dari ExpectedException adalah bahwa Anda tidak hanya dapat menentukan pengecualian yang diharapkan, tetapi Anda juga dapat menentukan properti pengecualian tertentu seperti penyebab yang diharapkan atau pesan yang diharapkan.
Jason Thompson
Dugaan saya adalah bahwa solusi ini memiliki beberapa kelemahan yang sama dengan mengejek? Misalnya, apakah fooini finalakan gagal karena Anda tidak dapat proksi foo?
Tom
Tom, jika doStuff () adalah bagian dari antarmuka pendekatan proxy akan berfungsi. Kalau tidak, pendekatan ini akan gagal, Anda benar.
rwitzel
31

Update: JUnit5 memiliki perbaikan untuk pengecualian pengujian: assertThrows.

contoh berikut adalah dari: Panduan Pengguna Junit 5

 @Test
void exceptionTesting() {
    Throwable exception = assertThrows(IllegalArgumentException.class, () -> 
    {
        throw new IllegalArgumentException("a message");
    });
    assertEquals("a message", exception.getMessage());
}

Jawaban asli menggunakan JUnit 4.

Ada beberapa cara untuk menguji apakah pengecualian dilemparkan. Saya juga telah membahas opsi di bawah ini dalam posting saya. Bagaimana cara menulis unit test dengan JUnit?

Atur expectedparameternya @Test(expected = FileNotFoundException.class).

@Test(expected = FileNotFoundException.class) 
public void testReadFile() { 
    myClass.readFile("test.txt");
}

Menggunakan try catch

public void testReadFile() { 
    try {
        myClass.readFile("test.txt");
        fail("Expected a FileNotFoundException to be thrown");
    } catch (FileNotFoundException e) {
        assertThat(e.getMessage(), is("The file test.txt does not exist!"));
    }

}

Pengujian dengan ExpectedExceptionAturan.

@Rule
public ExpectedException thrown = ExpectedException.none();

@Test
public void testReadFile() throws FileNotFoundException {

    thrown.expect(FileNotFoundException.class);
    thrown.expectMessage(startsWith("The file test.txt"));
    myClass.readFile("test.txt");
}

Anda dapat membaca lebih lanjut tentang pengujian pengecualian di wiki JUnit4 untuk pengujian Pengecualian dan bad.robot - Mengharapkan Pengecualian Aturan JUnit .

Dilini Rajapaksha
sumber
22

Anda juga dapat melakukan ini:

@Test
public void testFooThrowsIndexOutOfBoundsException() {
    try {
        foo.doStuff();
        assert false;
    } catch (IndexOutOfBoundsException e) {
        assert true;
    }
}
John Mikic
sumber
12
Dalam tes JUnit, lebih baik digunakan Assert.fail(), tidak assert, kalau-kalau tes Anda berjalan di lingkungan di mana pernyataan tidak diaktifkan.
NamshubWriter
14

IMHO, cara terbaik untuk memeriksa pengecualian di JUnit adalah pola coba / tangkap / gagal / tegaskan:

// this try block should be as small as possible,
// as you want to make sure you only catch exceptions from your code
try {
    sut.doThing();
    fail(); // fail if this does not throw any exception
} catch(MyException e) { // only catch the exception you expect,
                         // otherwise you may catch an exception for a dependency unexpectedly
    // a strong assertion on the message, 
    // in case the exception comes from anywhere an unexpected line of code,
    // especially important if your checking IllegalArgumentExceptions
    assertEquals("the message I get", e.getMessage()); 
}

The assertTruemungkin agak kuat bagi sebagian orang, jadi assertThat(e.getMessage(), containsString("the message");mungkin lebih baik.

Alex Collins
sumber
13

Jawaban paling fleksibel dan elegan untuk Junit 4 saya temukan di blog Mkyong . Ini memiliki fleksibilitastry/catch menggunakan @Ruleanotasi. Saya suka pendekatan ini karena Anda dapat membaca atribut spesifik pengecualian khusus.

package com.mkyong;

import com.mkyong.examples.CustomerService;
import com.mkyong.examples.exception.NameNotFoundException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.hasProperty;

public class Exception3Test {

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    public void testNameNotFoundException() throws NameNotFoundException {

        //test specific type of exception
        thrown.expect(NameNotFoundException.class);

        //test message
        thrown.expectMessage(is("Name is empty!"));

        //test detail
        thrown.expect(hasProperty("errCode"));  //make sure getters n setters are defined.
        thrown.expect(hasProperty("errCode", is(666)));

        CustomerService cust = new CustomerService();
        cust.findByName("");

    }

}
Dherik
sumber
12

Saya mencoba banyak metode di sini, tetapi mereka rumit atau tidak memenuhi persyaratan saya. Bahkan, seseorang dapat menulis metode pembantu dengan cukup sederhana:

public class ExceptionAssertions {
    public static void assertException(BlastContainer blastContainer ) {
        boolean caughtException = false;
        try {
            blastContainer.test();
        } catch( Exception e ) {
            caughtException = true;
        }
        if( !caughtException ) {
            throw new AssertionFailedError("exception expected to be thrown, but was not");
        }
    }
    public static interface BlastContainer {
        public void test() throws Exception;
    }
}

Gunakan seperti ini:

assertException(new BlastContainer() {
    @Override
    public void test() throws Exception {
        doSomethingThatShouldExceptHere();
    }
});

Zero dependency: tidak perlu untuk mockito, tidak perlu powermock; dan bekerja dengan baik dengan kelas akhir.

Hugh Perkins
sumber
Menarik, tetapi tidak sesuai dengan AAA (Atur Tindakan Undang-Undang), di mana Anda ingin melakukan tindakan dan langkah Pertegas dalam langkah-langkah yang sebenarnya berbeda.
bln-tom
1
@ bln-tom Secara teknis ini adalah dua langkah yang berbeda, mereka hanya tidak dalam urutan itu. ; p
Trejkaz
10

Solusi Java 8

Jika Anda menginginkan solusi yang:

  • Memanfaatkan Java 8 lambdas
  • Apakah tidak bergantung pada sihir JUnit
  • Memungkinkan Anda memeriksa beberapa pengecualian dalam satu metode pengujian
  • Memeriksa pengecualian yang dilemparkan oleh serangkaian garis tertentu dalam metode pengujian Anda, bukan garis apa pun yang tidak diketahui di seluruh metode pengujian
  • Menghasilkan objek pengecualian aktual yang dilemparkan sehingga Anda dapat memeriksanya lebih lanjut

Berikut adalah fungsi utilitas yang saya tulis:

public final <T extends Throwable> T expectException( Class<T> exceptionClass, Runnable runnable )
{
    try
    {
        runnable.run();
    }
    catch( Throwable throwable )
    {
        if( throwable instanceof AssertionError && throwable.getCause() != null )
            throwable = throwable.getCause(); //allows "assert x != null : new IllegalArgumentException();"
        assert exceptionClass.isInstance( throwable ) : throwable; //exception of the wrong kind was thrown.
        assert throwable.getClass() == exceptionClass : throwable; //exception thrown was a subclass, but not the exact class, expected.
        @SuppressWarnings( "unchecked" )
        T result = (T)throwable;
        return result;
    }
    assert false; //expected exception was not thrown.
    return null; //to keep the compiler happy.
}

( diambil dari blog saya )

Gunakan sebagai berikut:

@Test
public void testThrows()
{
    RuntimeException e = expectException( RuntimeException.class, () -> 
        {
            throw new RuntimeException( "fail!" );
        } );
    assert e.getMessage().equals( "fail!" );
}
Mike Nakis
sumber
8

Dalam kasus saya, saya selalu mendapatkan RuntimeException dari db, tetapi pesan berbeda. Dan pengecualian harus ditangani masing-masing. Inilah cara saya mengujinya:

@Test
public void testThrowsExceptionWhenWrongSku() {

    // Given
    String articleSimpleSku = "999-999";
    int amountOfTransactions = 1;
    Exception exception = null;

    // When
    try {
        createNInboundTransactionsForSku(amountOfTransactions, articleSimpleSku);
    } catch (RuntimeException e) {
        exception = e;
    }

    // Then
    shouldValidateThrowsExceptionWithMessage(exception, MESSAGE_NON_EXISTENT_SKU);
}

private void shouldValidateThrowsExceptionWithMessage(final Exception e, final String message) {
    assertNotNull(e);
    assertTrue(e.getMessage().contains(message));
}
Macchiatow
sumber
1
sebelum garis dengan } catch (, Anda harus memasukkanfail("no exception thrown");
Daniel Alder
6

Buat saja Pencocokan yang bisa dimatikan dan dinyalakan, seperti ini:

public class ExceptionMatcher extends BaseMatcher<Throwable> {
    private boolean active = true;
    private Class<? extends Throwable> throwable;

    public ExceptionMatcher(Class<? extends Throwable> throwable) {
        this.throwable = throwable;
    }

    public void on() {
        this.active = true;
    }

    public void off() {
        this.active = false;
    }

    @Override
    public boolean matches(Object object) {
        return active && throwable.isAssignableFrom(object.getClass());
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("not the covered exception type");
    }
}

Untuk menggunakannya:

tambahkan public ExpectedException exception = ExpectedException.none();, lalu:

ExceptionMatcher exMatch = new ExceptionMatcher(MyException.class);
exception.expect(exMatch);
someObject.somethingThatThrowsMyException();
exMatch.off();
Tor P
sumber
6

Di JUnit 4 atau lebih baru Anda dapat menguji pengecualian sebagai berikut

@Rule
public ExpectedException exceptions = ExpectedException.none();


ini menyediakan banyak fitur yang dapat digunakan untuk meningkatkan tes JUnit kami.
Jika Anda melihat contoh di bawah ini saya menguji 3 hal dengan pengecualian.

  1. Jenis pengecualian yang dilemparkan
  2. Pesan pengecualian
  3. Penyebab pengecualian


public class MyTest {

    @Rule
    public ExpectedException exceptions = ExpectedException.none();

    ClassUnderTest classUnderTest;

    @Before
    public void setUp() throws Exception {
        classUnderTest = new ClassUnderTest();
    }

    @Test
    public void testAppleisSweetAndRed() throws Exception {

        exceptions.expect(Exception.class);
        exceptions.expectMessage("this is the exception message");
        exceptions.expectCause(Matchers.<Throwable>equalTo(exceptionCause));

        classUnderTest.methodUnderTest("param1", "param2");
    }

}
JoBⅈN
sumber
6

Kita bisa menggunakan pernyataan gagal setelah metode yang harus mengembalikan pengecualian:

try{
   methodThatThrowMyException();
   Assert.fail("MyException is not thrown !");
} catch (final Exception exception) {
   // Verify if the thrown exception is instance of MyException, otherwise throws an assert failure
   assertTrue(exception instanceof MyException, "An exception other than MyException is thrown !");
   // In case of verifying the error message
   MyException myException = (MyException) exception;
   assertEquals("EXPECTED ERROR MESSAGE", myException.getMessage());
}
Shessuky
sumber
3
Yang kedua catchakan menelan jejak stack jika beberapa pengecualian lain dilemparkan, kehilangan informasi yang bermanfaat
NamshubWriter
5

Selain apa yang dikatakan NamShubWriter , pastikan bahwa:

  • Contoh ExpectedException bersifat publik ( Pertanyaan Terkait )
  • ExpectedException tidak dipakai dalam katakanlah, metode @Before. Posting ini dengan jelas menjelaskan semua seluk-beluk urutan eksekusi JUnit.

Jangan tidak melakukan hal ini:

@Rule    
public ExpectedException expectedException;

@Before
public void setup()
{
    expectedException = ExpectedException.none();
}

Akhirnya, posting blog ini dengan jelas menggambarkan cara untuk menegaskan bahwa pengecualian tertentu dilemparkan.

Bruce Wayne
sumber
4

Saya merekomendasikan perpustakaan assertj-coreuntuk menangani pengecualian dalam tes junit

Di java 8, seperti ini:

//given

//when
Throwable throwable = catchThrowable(() -> anyService.anyMethod(object));

//then
AnyException anyException = (AnyException) throwable;
assertThat(anyException.getMessage()).isEqualTo("........");
assertThat(exception.getCode()).isEqualTo(".......);
Piotr R
sumber
2

Solusi Junit4 dengan Java8 adalah dengan menggunakan fungsi ini:

public Throwable assertThrows(Class<? extends Throwable> expectedException, java.util.concurrent.Callable<?> funky) {
    try {
        funky.call();
    } catch (Throwable e) {
        if (expectedException.isInstance(e)) {
            return e;
        }
        throw new AssertionError(
                String.format("Expected [%s] to be thrown, but was [%s]", expectedException, e));
    }
    throw new AssertionError(
            String.format("Expected [%s] to be thrown, but nothing was thrown.", expectedException));
}

Maka penggunaan adalah:

    assertThrows(ValidationException.class,
            () -> finalObject.checkSomething(null));

Perhatikan bahwa satu-satunya batasan adalah menggunakan finalreferensi objek dalam ekspresi lambda. Solusi ini memungkinkan untuk melanjutkan pernyataan pengujian alih-alih mengharapkan yang dapat dicapai pada tingkat metode menggunakan @Test(expected = IndexOutOfBoundsException.class)solusi.

Donatello
sumber
1

Ambil contoh, Anda ingin menulis Junit untuk fragmen kode yang disebutkan di bawah ini

public int divideByZeroDemo(int a,int b){

    return a/b;
}

public void exceptionWithMessage(String [] arr){

    throw new ArrayIndexOutOfBoundsException("Array is out of bound");
}

Kode di atas adalah untuk menguji beberapa pengecualian yang tidak diketahui yang mungkin terjadi dan yang di bawah ini adalah untuk menegaskan beberapa pengecualian dengan pesan khusus.

 @Rule
public ExpectedException exception=ExpectedException.none();

private Demo demo;
@Before
public void setup(){

    demo=new Demo();
}
@Test(expected=ArithmeticException.class)
public void testIfItThrowsAnyException() {

    demo.divideByZeroDemo(5, 0);

}

@Test
public void testExceptionWithMessage(){


    exception.expectMessage("Array is out of bound");
    exception.expect(ArrayIndexOutOfBoundsException.class);
    demo.exceptionWithMessage(new String[]{"This","is","a","demo"});
}
Shirsh Sinha
sumber
1
    @Test(expectedException=IndexOutOfBoundsException.class) 
    public void  testFooThrowsIndexOutOfBoundsException() throws Exception {
         doThrow(IndexOutOfBoundsException.class).when(foo).doStuff();  
         try {
             foo.doStuff(); 
            } catch (IndexOutOfBoundsException e) {
                       assertEquals(IndexOutOfBoundsException .class, ex.getCause().getClass());
                      throw e;

               }

    }

Berikut cara lain untuk mengecek metode melempar pengecualian yang benar atau tidak.

MangduYogii
sumber
1

Kerangka kerja JUnit memiliki assertThrows()metode:

ArithmeticException exception = assertThrows(ArithmeticException.class, () ->
    calculator.divide(1, 0));
assertEquals("/ by zero", exception.getMessage());
Lu55
sumber
0

Dengan Java 8 Anda dapat membuat metode mengambil kode untuk memeriksa dan pengecualian yang diharapkan sebagai parameter:

private void expectException(Runnable r, Class<?> clazz) { 
    try {
      r.run();
      fail("Expected: " + clazz.getSimpleName() + " but not thrown");
    } catch (Exception e) {
      if (!clazz.isInstance(e)) fail("Expected: " + clazz.getSimpleName() + " but " + e.getClass().getSimpleName() + " found", e);
    }
  }

dan kemudian di dalam tes Anda:

expectException(() -> list.sublist(0, 2).get(2), IndexOutOfBoundsException.class);

Manfaat:

  • tidak bergantung pada perpustakaan apa pun
  • cek terlokalisasi - lebih tepat dan memungkinkan untuk memiliki beberapa pernyataan seperti ini dalam satu pengujian jika diperlukan
  • mudah digunakan
fahrenx
sumber