Bagaimana cara menegaskan pesan pengecualian saya dengan anotasi Tes JUnit?

315

Saya telah menulis beberapa tes JUnit dengan @Testanotasi. Jika metode pengujian saya melempar pengecualian yang diperiksa dan jika saya ingin menegaskan pesan bersama dengan pengecualian, apakah ada cara untuk melakukannya dengan @Testanotasi JUnit ? AFAIK, JUnit 4.7 tidak menyediakan fitur ini tetapi apakah ada versi di masa depan yang menyediakannya? Saya tahu di .NET Anda dapat menegaskan pesan dan kelas pengecualian. Mencari fitur serupa di dunia Java.

Ini yang aku inginkan:

@Test (expected = RuntimeException.class, message = "Employee ID is null")
public void shouldThrowRuntimeExceptionWhenEmployeeIDisNull() {}
Cshah
sumber
1
Sekarang saya memikirkannya sedikit lagi ... Apakah Anda yakin itu adalah ide yang baik untuk menegaskan pesan? Pertanyaan Anda membuat saya menggali sedikit kode sumber junit dan sepertinya mereka bisa dengan mudah menambahkan fitur ini. Fakta bahwa mereka tidak , membuat saya berpikir itu mungkin tidak dianggap sebagai praktik yang baik. Mengapa penting dalam proyek Anda untuk menegaskan pesan?
c_maker
10
pertanyaan yang bagus. Katakan bahwa metode yang berisi 15 baris kode melempar pengecualian yang sama dari 2 tempat yang berbeda. Kasing uji saya perlu menegaskan tidak hanya kelas pengecualian tetapi juga pesan di dalamnya. Dalam dunia yang ideal, setiap perilaku abnormal harus memiliki pengecualiannya sendiri. Jika itu yang terjadi, pertanyaan saya tidak akan pernah muncul tetapi aplikasi produksi tidak memiliki pengecualian kebiasaan unik untuk setiap perilaku abnormal.
Cshah
Sebagai catatan - ada @expectedExceptionMessageanotasi di PHPUnit.
bancer

Jawaban:

535

Anda dapat menggunakan @Ruleanotasi dengan ExpectedException, seperti ini:

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

@Test
public void shouldThrowRuntimeExceptionWhenEmployeeIDisNull() throws Exception {
    expectedEx.expect(RuntimeException.class);
    expectedEx.expectMessage("Employee ID is null");

    // do something that should throw the exception...
    System.out.println("=======Starting Exception process=======");
    throw new NullPointerException("Employee ID is null");
}

Perhatikan bahwa contoh dalam ExpectedExceptiondokumen tersebut (saat ini) salah - tidak ada konstruktor publik, jadi Anda harus menggunakannya ExpectedException.none().

Jesse Merriman
sumber
1
Catatan: Bagi saya ketika expectMessageditentukan sebagai string kosong, perbandingan untuk pesan tidak dilakukan
redDevil
1
Berguna untukku. Terima kasih. Metode pengujian harus memiliki throws RuntimeExceptionsetelah menambahkan kode yang melempar pengecualian. Jangan tangkap ...
Bumbolt
5
Saya pribadi tidak ingin menggunakan ini karena membuat bidang untuk tujuan sekelompok kecil metode adalah praktik yang buruk. Bukan kritik atas responnya, tapi desain JUnit. Solusi hipotetis OP akan jauh lebih baik jika ada.
Sridhar Sarnobat
2
@redDevil: ExpectedMessage memeriksa apakah pesan kesalahan "berisi" string yang ditentukan dalam fungsi ini (seperti substring dari pesan kesalahan)
tuan.dinh
3
expectMessage dengan parameter string melakukan pengecekan String.contains, untuk kecocokan yang tepat dari pesan pengecualian, gunakan pencocokan failure.expectMessage(CoreMatchers.equalTo(...))
hamcrest
42

Saya suka @Rulejawabannya. Namun, jika karena alasan tertentu Anda tidak ingin menggunakan aturan. Ada opsi ketiga.

@Test (expected = RuntimeException.class)
public void myTestMethod()
{
   try
   {
      //Run exception throwing operation here
   }
   catch(RuntimeException re)
   {
      String message = "Employee ID is null";
      assertEquals(message, re.getMessage());
      throw re;
    }
    fail("Employee Id Null exception did not throw!");
  }
Badai badai
sumber
32

Apakah Anda harus menggunakan @Test(expected=SomeException.class)? Ketika kita harus menegaskan pesan pengecualian yang sebenarnya, inilah yang kita lakukan.

@Test
public void myTestMethod()
{
  try
  {
    final Integer employeeId = null;
    new Employee(employeeId);
    fail("Should have thrown SomeException but did not!");
  }
  catch( final SomeException e )
  {
    final String msg = "Employee ID is null";
    assertEquals(msg, e.getMessage());
  }
}
c_maker
sumber
6
Saya sadar akan menulis blok tangkap dan menggunakan menegaskan dalam itu tetapi untuk pembacaan kode yang lebih baik saya ingin lakukan dengan anotasi.
Cshah
Anda juga tidak akan mendapatkan pesan yang bagus seperti saat melakukannya dengan cara yang "benar".
NeplatnyUdaj
15
Masalah dengan versi coba / tangkap, sekarang JUnit menyediakan @Test(expected=...)dan ExpectedException, adalah bahwa saya telah melihat pada banyak kesempatan seseorang lupa untuk menelepon fail()di akhir tryblok . Jika tidak tertangkap oleh tinjauan kode, tes Anda mungkin salah-positif dan selalu lulus.
William Harga
Inilah sebabnya saya tidak suka semua hal deklaratif ini. Itu membuat sulit untuk mengakses apa yang Anda inginkan.
Sridhar Sarnobat
30

Di JUnit 4.13 Anda dapat melakukan:

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;

...

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

  assertEquals("a message", exception.getMessage());
}

Ini juga berfungsi di JUnit 5 tetapi dengan impor berbeda:

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

...
Johan Boberg
sumber
Suka solusi ini. Harus pindah ke JUnit 5.
WesternGun
Gaaaaaaaaa. 4,13 masih dalam versi beta hingga hari ini (Musim Gugur, 2019)? mvnrepository.com/artifact/junit/junit
granadaCoder
v4.13 tidak dalam keadaan beta lagi (rilis pada Januari 2020)
Simon
11

Sebenarnya, penggunaan terbaik adalah dengan coba / tangkap. Mengapa? Karena Anda dapat mengontrol tempat di mana Anda mengharapkan pengecualian.

Pertimbangkan contoh ini:

@Test (expected = RuntimeException.class)
public void someTest() {
   // test preparation
   // actual test
}

Bagaimana jika suatu hari kode dimodifikasi dan persiapan ujian akan melempar RuntimeException? Dalam hal ini tes yang sebenarnya bahkan tidak diuji dan bahkan jika itu tidak membuang pengecualian tes akan berlalu.

Itulah mengapa jauh lebih baik menggunakan try / catch daripada mengandalkan anotasi.

Krzysztof Cislo
sumber
Sayangnya, ini juga jawaban saya.
Sridhar Sarnobat
2
Kekhawatiran tentang perubahan kode dikurangi dengan memiliki kasus uji kecil, khusus permutasi. Terkadang hal ini tidak terhindarkan dan kita harus mengandalkan metode catch / try, tetapi jika itu sering terjadi, kemungkinan besar kita perlu merevisi cara kita menulis fungsi case test kita.
luis.espinal
Itu masalah dengan tes dan / atau kode Anda. Anda TIDAK mengharapkan RuntimeException umum, Anda mengharapkan pengecualian khusus, atau setidaknya pesan tertentu.
DennisK
Saya menggunakan RuntimeExceptionsebagai contoh, ganti pengecualian ini dengan pengecualian lainnya.
Krzysztof Cislo
8

Raystorm punya jawaban yang bagus. Saya juga bukan penggemar berat Aturan. Saya melakukan sesuatu yang serupa, kecuali bahwa saya membuat kelas utilitas berikut untuk membantu keterbacaan dan kegunaan, yang merupakan salah satu plus besar anotasi di tempat pertama.

Tambahkan kelas utilitas ini:

import org.junit.Assert;

public abstract class ExpectedRuntimeExceptionAsserter {

    private String expectedExceptionMessage;

    public ExpectedRuntimeExceptionAsserter(String expectedExceptionMessage) {
        this.expectedExceptionMessage = expectedExceptionMessage;
    }

    public final void run(){
        try{
            expectException();
            Assert.fail(String.format("Expected a RuntimeException '%s'", expectedExceptionMessage));
        } catch (RuntimeException e){
            Assert.assertEquals("RuntimeException caught, but unexpected message", expectedExceptionMessage, e.getMessage());
        }
    }

    protected abstract void expectException();

}

Kemudian untuk pengujian unit saya, yang saya butuhkan adalah kode ini:

@Test
public void verifyAnonymousUserCantAccessPrivilegedResourceTest(){
    new ExpectedRuntimeExceptionAsserter("anonymous user can't access privileged resource"){
        @Override
        protected void expectException() {
            throw new RuntimeException("anonymous user can't access privileged resource");
        }
    }.run(); //passes test; expected exception is caught, and this @Test returns normally as "Passed"
}
user64141
sumber
2

Jika menggunakan @Rule, set pengecualian diterapkan ke semua metode pengujian di kelas Tes.

Jyothi
sumber
2
Menggunakan respons Jesse Merriman, pengecualian hanya diperiksa dalam metode pengujian yang memanggil ke diharapkanEx.expect () dan expectedEx.expectMessage (). Metode lain akan menggunakan definisi yang diharapkanEx = ExpectedException.none (), yaitu, tidak terkecuali yang diharapkan.
Egl
2

Saya tidak pernah menyukai cara menyatakan pengecualian dengan Junit. Jika saya menggunakan "yang diharapkan" dalam anotasi, tampaknya dari sudut pandang saya, kami melanggar pola "diberikan, kapan, lalu" karena "maka" ditempatkan di bagian atas definisi pengujian.

Juga, jika kita menggunakan "Aturan", kita harus berurusan dengan kode boilerplate begitu banyak. Jadi, jika Anda dapat menginstal pustaka baru untuk tes Anda, saya sarankan untuk melihat AssertJ (pustaka yang sekarang dilengkapi dengan SpringBoot)

Kemudian tes yang tidak melanggar prinsip "diberikan / kapan / kemudian", dan itu dilakukan dengan menggunakan AssertJ untuk memverifikasi:

1 - Pengecualian adalah yang kami harapkan. 2 - Ini juga pesan yang diharapkan

Akan terlihat seperti ini:

 @Test
void should_throwIllegalUse_when_idNotGiven() {

    //when
    final Throwable raisedException = catchThrowable(() -> getUserDAO.byId(null));

    //then
    assertThat(raisedException).isInstanceOf(IllegalArgumentException.class)
            .hasMessageContaining("Id to fetch is mandatory");
}
geeksusma
sumber
1

Saya suka jawaban user64141 tetapi menemukan bahwa itu bisa lebih digeneralisasi. Inilah pendapat saya:

public abstract class ExpectedThrowableAsserter implements Runnable {

    private final Class<? extends Throwable> throwableClass;
    private final String expectedExceptionMessage;

    protected ExpectedThrowableAsserter(Class<? extends Throwable> throwableClass, String expectedExceptionMessage) {
        this.throwableClass = throwableClass;
        this.expectedExceptionMessage = expectedExceptionMessage;
    }

    public final void run() {
        try {
            expectException();
        } catch (Throwable e) {
            assertTrue(String.format("Caught unexpected %s", e.getClass().getSimpleName()), throwableClass.isInstance(e));
            assertEquals(String.format("%s caught, but unexpected message", throwableClass.getSimpleName()), expectedExceptionMessage, e.getMessage());
            return;
        }
        fail(String.format("Expected %s, but no exception was thrown.", throwableClass.getSimpleName()));
    }

    protected abstract void expectException();

}

Perhatikan bahwa meninggalkan pernyataan "gagal" dalam blok uji coba menyebabkan pengecualian pernyataan terkait ditangkap; menggunakan return dalam pernyataan catch mencegah hal ini.

Addison Crump
sumber
0

Impor perpustakaan catch-exception , dan gunakan itu. Ini jauh lebih bersih daripada ExpectedExceptionaturan atau try-catch.

Contoh dari dokumen mereka:

import static com.googlecode.catchexception.CatchException.*;
import static com.googlecode.catchexception.apis.CatchExceptionHamcrestMatchers.*;

// given: an empty list
List myList = new ArrayList();

// when: we try to get the first element of the list
catchException(myList).get(1);

// then: we expect an IndexOutOfBoundsException with message "Index: 1, Size: 0"
assertThat(caughtException(),
  allOf(
    instanceOf(IndexOutOfBoundsException.class),
    hasMessage("Index: 1, Size: 0"),
    hasNoCause()
  )
);
whistling_marmot
sumber
-2
@Test (expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "This is not allowed")
public void testInvalidValidation() throws Exception{
     //test code
}
aasha
sumber
Dapatkah seseorang membantu saya memahami mengapa jawaban ini adalah -1
aasha
Pertanyaannya adalah meminta Junittetapi jawaban Anda adalah memberiTestNG
Huazhe Yin