如何使用JUnit来测试某些代码是否抛出异常?

我当然可以这样做:

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

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

  assertTrue(thrown);
}

我记得,对于这类情况,有一个注释或Assert.xyz之类的东西远没有JUnit那么笨拙,更符合JUnit的精神。


当前回答

这样如何:捕获一个非常普通的异常,确保它从catch块中取出,然后断言该异常的类是您期望的。如果a)异常类型错误(例如,如果您使用了空指针),并且b)该异常从未被抛出,则该断言将失败。

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

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

  assertTrue(e instanceof IndexOutOfBoundsException);
}

其他回答

编辑:现在JUnit 5和JUnit 4.13已经发布,最好的选择是使用Assertions.assertThrows()(针对JUnit 5)和Assertions.AssertThrow()(对于JUnit 4.13+)。有关详细信息,请参阅我的其他答案。

如果您尚未迁移到JUnit 5,但可以使用JUnit 4.7,则可以使用ExpectedException规则:

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

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

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

这比@Test(预期=IndexOutOfBoundsException.class)好得多,因为如果在foo.doStuff()之前抛出IndexOutofBoundsExcept,测试将失败

有关详细信息,请参阅本文。

例如,您想为下面提到的代码片段编写Junit

public int divideByZeroDemo(int a,int b){

    return a/b;
}

public void exceptionWithMessage(String [] arr){

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

上面的代码用于测试可能发生的某些未知异常,下面的代码用于使用自定义消息断言某些异常。

 @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"});
}

这取决于JUnit版本和使用的断言库。

JUnit5和4.13见答案如果您使用AssertJ或google truth,请参阅答案

JUnit<=4.12的原始答案是:

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

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

    }

尽管答案对于JUnit<=4.12有更多选项。

参考:

JUnit测试常见问题解答

我想评论一下这个问题的解决方案,它避免了任何与异常相关的JUnit代码。

我使用assertTrue(布尔值)和try/catch组合来查找要抛出的预期异常。下面是一个示例:

public void testConstructor() {
    boolean expectedExceptionThrown;
    try {
        // Call constructor with bad arguments
        double a = 1;
        double b = 2;
        double c = a + b; // In my example, this is an invalid option for c
        new Triangle(a, b, c);
        expectedExceptionThrown = false; // because it successfully constructed the object
    }
    catch(IllegalArgumentException e) {
        expectedExceptionThrown = true; // because I'm in this catch block
    }
    catch(Exception e) {
        expectedExceptionThrown = false; // because it threw an exception but not the one expected
    }
    assertTrue(expectedExceptionThrown);
}

tl;博士

JDK8后:使用AssertJ或自定义lambdas断言异常行为。JDK8之前的版本:我将推荐旧的好的try-catch块。(不要忘记在catch块之前添加fail()断言)

无论Junit 4还是Junit 5。

长话短说

可以自己编写一个自己动手的try-catch块或使用JUnit工具(@Test(expected=…)或@Rule ExpectedException JUnit规则特性)。

但这些方式并不那么优雅,也不能很好地将可读性与其他工具结合起来。此外,JUnit工具确实存在一些缺陷。

try-catch块必须围绕测试行为编写块,并在catch块中写入断言,这可能很好,但许多人发现这种样式会中断测试的读取流程。此外,您需要在try块的末尾编写Assert.fail。否则,测试可能会漏掉断言的一面;PMD、findbugs或Sonar将发现此类问题。@Test(expected=…)特性很有趣,因为你可以编写更少的代码,然后编写这个测试就不太容易出现编码错误。但在某些领域缺乏这种方法。如果测试需要检查异常的其他内容,如原因或消息(好的异常消息非常重要,只有精确的异常类型可能不够)。同样,由于在方法中放置了期望值,根据测试代码的编写方式,测试代码的错误部分可能会引发异常,导致测试结果为假阳性,我不确定PMD、findbugs或Sonar是否会给出此类代码的提示。@测试(应为=WantedException.class)public void call2_should_throw_a_WantedException__not_call1(){//初始化测试tested.call1();//可能引发WantedException//待实际测试的呼叫tested.call2();//应该引发异常的调用}ExpectedException规则也是一种修复之前警告的尝试,但使用起来有点尴尬,因为它使用了预期风格,EasyMock用户非常了解这种风格。这对某些人来说可能很方便,但如果你遵循行为驱动开发(BDD)或安排行为断言(AAA)原则,ExpectedException规则将不适合这些写作风格。除此之外,它可能会遇到与@Test方法相同的问题,这取决于您将期望放在何处。@抛出的规则ExpectedException=ExpectedException.none()@测试public void call2_should_throw_a_WantedException__not_call1(){//期望抛出.exexpect(WantedException.class);抛出.expectMessage(“boom”);//初始化测试tested.call1();//可能引发WantedException//待实际测试的呼叫tested.call2();//应该引发异常的调用}即使预期的异常被放置在测试语句之前,如果测试遵循BDD或AAA,它也会破坏您的阅读流程。另外,请参阅ExpectedException作者JUnit上的这个评论问题。JUnit 4.13-beta-2甚至反对这种机制:拉取请求#1519:预期不推荐异常Assert.assertThrows方法为验证异常提供了更好的方法。此外,当与TestWatcher等其他规则一起使用时,ExpectedException的使用容易出错,因为在这种情况下,规则的顺序很重要。

因此,以上这些选项都有其所有的警告,并且显然不能避免编码器错误。

有一个项目是我在创建这个答案后意识到的,看起来很有希望,那就是catch exception。正如项目描述所说,它让程序员编写一行流畅的代码来捕捉异常,并为后一个断言提供这个异常。您可以使用任何断言库,如Hamcrest或AssertJ。主页上的一个快速示例://给定:空列表List myList=新建ArrayList();//当:我们尝试获取列表的第一个元素当(myList).get(1);//那么:我们需要IndexOutOfBoundsException然后(caughtException()).isInstanceOf(IndexOutOfBoundsException.class).hasMessage(“索引:1,大小:0”).hhasNoCause();正如您所看到的,代码非常简单,您在特定的行上捕捉到异常,然后API是一个别名,它将使用AssertJ API(类似于使用assertThat(ex).hasNoCause()…)。在某些时候,该项目依赖于FEST Assert,即AssertJ的祖先。编辑:该项目似乎正在酝酿对Java 8 Lambdas的支持。目前,该库有两个缺点:在撰写本文时,值得注意的是,这个库是基于Mockito 1.x的,因为它在后台创建了一个被测试对象的模拟。由于Mockito仍然没有更新,所以这个库不能与final类或final方法一起使用。即使它是基于当前版本中的Mockito 2,这也需要声明一个全局模拟生成器(内联模拟生成器),这可能不是您想要的,因为这个模拟生成器具有与常规模拟生成器不同的缺点。它还需要另一个测试依赖项。一旦库支持lambdas,这些问题就不适用了。但是,AssertJ工具集将复制该功能。如果您不想使用catch异常工具,请考虑所有这些,我将推荐try-catch块的旧好方法,至少到JDK7为止。对于JDK8用户,您可能更喜欢使用AssertJ,因为它提供的可能不仅仅是断言异常。有了JDK8,lambdas进入了测试场景,事实证明,这是一种很有意思的方式来断言异常行为。AssertJ已经更新,提供了一个非常流畅的API来断言异常行为。以及AssertJ的示例测试:@测试public void test_exception_approach_1(){...assertThatExceptionOfType(IOException.class).isThrownBy(()->someBadIOOperation()).带有消息(“boom!”);}@测试public void test_exception_approach_2(){...assertThatThrownBy(()->someBadIOOperation()).isInstanceOf(异常.class).hasMessageContaining(“boom”);}@测试public void test_exception_approach_3(){...//当Throwable throw=catchThrowable(()->someBadIOOperation());//然后assertThat(引发).isInstanceOf(Exception.class).hasMessageContaining(“boom”);}随着JUnit5的几乎完全重写,断言得到了一些改进,它们可能会被证明是一种开箱即用的正确断言异常的方法。但实际上,断言API仍然有点差,除了assertThrows之外没有其他东西。@测试@DisplayName(“偷看时抛出EmptyStackException”)void throwsExceptionWhenPeeked(){Throwable t=assertThrows(EmptyStackException.class,()->stack.ppeek());断言.assertEquals(“…”,t.getMessage());}正如您所注意到的,assertEquals仍然返回void,因此不允许像AssertJ那样链接断言。此外,如果您记得与Matcher或Assert的名称冲突,请准备好与Assertions的名称冲突。

我想总结一下,今天(2017-03-03)AssertJ的易用性、可发现的API、快速的开发速度以及事实上的测试依赖性是JDK8的最佳解决方案,无论测试框架(JUnit与否)如何,以前的JDK都应该依赖try-catch块,即使它们觉得笨拙。

这个答案是从另一个不具有相同可见性的问题中复制的,我是同一个作者。