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

我当然可以这样做:

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

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

  assertTrue(thrown);
}

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


当前回答

JUnit框架具有assertThrows()方法:

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

对于JUnit 5,它位于org.unit.jupiter.api.Assertions类中;对于JUnit 4.13,它位于org.JUnit.Assert类中;对于JUnit4的早期版本:只需将org.JUnit.jupiter:junitjupiterapi上的引用添加到项目中,就可以从JUnit5获得非常好的工作版本。

其他回答

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块,即使它们觉得笨拙。

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

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

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

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

  assertTrue(e instanceof IndexOutOfBoundsException);
}
try {
    my method();
    fail( "This method must thrwo" );
} catch (Exception ex) {
    assertThat(ex.getMessage()).isEqual(myErrormsg);
}

JUnit内置了对此的支持,并带有“expected”属性。

除了NamShubWriter所说的,还要确保:

ExpectedException实例是公共的(相关问题)ExpectedException未在@Before方法中实例化。这篇文章清楚地解释了JUnit执行顺序的所有复杂性。

不要这样做:

@Rule    
public ExpectedException expectedException;

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

最后,这篇博客文章清楚地说明了如何断言抛出了某个异常。