您如何断言在 JUnit 4 测试中引发了某种异常?

如何惯用 JUnit4 来测试某些代码引发异常?

虽然我当然可以做这样的事情:

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

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

  assertTrue(thrown);
}

我记得在这种情况下,有一个批注或一个 Assert.xyz 或一些不那么杂乱无章的东西 ,更是 JUnit 的精髓。

答案

JUnit 4为此提供支持:

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

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

}

参考:

编辑:现在已经发布了 JUnit 5 和 JUnit 4.13,最好的选择是使用Assertions.assertThrows() (对于 JUnit 5)和Assert.assertThrows() (对于 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(expected=IndexOutOfBoundsException.class)好得多,因为如果在foo.doStuff()之前抛出IndexOutOfBoundsException则测试将失败。

详情请参阅本文

使用预期的异常时要小心,因为它仅断言该方法引发了该异常,而不是测试中的特定代码行

我倾向于将其用于测试参数验证,因为此类方法通常非常简单,但最好将更复杂的测试用于:

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

运用判断。

如前所述,在 JUnit 中有许多处理异常的方法。但是对于 Java 8,还有另一个:使用 Lambda 表达式。使用 Lambda 表达式,我们可以实现如下语法:

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

assertThrown 接受一个功能接口,可以使用 lambda 表达式,方法引用或构造函数引用创建其实例。 assertThrown 接受该接口将期望并准备处理异常。

这是相对简单但功能强大的技术。

看看描述此技术的博客文章: http : //blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html

可以在以下位置找到源代码: https : //github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8

披露:我是博客和项目的作者。

在 junit 中,有四种测试异常的方法。

junit5.x

  • 对于 junit5.x,可以按以下方式使用assertThrows

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

junit4.x

  • 对于 junit4.x,请使用测试注解的可选 “expected” 属性

    @Test(expected = IndexOutOfBoundsException.class)
    public void testFooThrowsIndexOutOfBoundsException() {
        foo.doStuff();
    }
  • 对于 junit4.x,请使用 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();
        }
    }
  • 您还可以使用在 junit 3 框架下广泛使用的经典 try / catch 方法

    @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.
        }
    }
  • 所以

    • 如果您喜欢 junit 5,那么您应该喜欢第一个
    • 当您只想测试异常类型时,使用第二种方法
    • 当您想进一步测试异常消息时,使用前两个和后两个
    • 如果您使用 junit 3,则首选第 4 个
  • 有关更多信息,您可以阅读此文档junit5 用户指南以了解详细信息。

tl; dr

  • JDK8 之前的版本:我将推荐旧的好try catch块。 ( 不要忘记在catch块之前添加fail()断言

  • JDK8 之后:使用 AssertJ 或自定义 lambda 来声明异常行为。

无论是 Junit 4 还是 JUnit 5。

长话

可以编写自己try 做的东西 catch块或使用 JUnit 工具( @Test(expected = ...)@Rule ExpectedException JUnit 规则功能)。

现在已经发布了 JUnit 5 和 JUnit 4.13,最好的选择是使用Assertions.assertThrows() (对于 JUnit 5)和Assert.assertThrows() (对于 JUnit 4.13)。请参阅《 Junit 5 用户指南》

这是一个验证抛出异常并使用Truth对异常消息进行断言的示例:

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

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

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

与其他答案中的方法相比,优点是:

  1. 内置于 JUnit
  2. 如果 lambda 中的代码未引发异常,则会得到一条有用的异常消息,如果 lambda 中的代码未引发异常,则会得到一个堆栈跟踪信息
  3. 简洁
  4. 允许您的测试遵循 “安排行为声明”
  5. 您可以精确指出要引发异常的代码
  6. 您无需在throws子句中列出预期的异常
  7. 您可以使用选择的断言框架对捕获的异常进行断言

类似的方法将被添加到 JUnit 4.13 中的org.junit Assert中。

怎么做:捕获一个非常普通的异常,确保它使它脱离 catch 块,然后断言该异常的类就是您期望的异常。如果 a)异常的类型错误(例如,如果您改为使用 Null 指针),并且 b)从未引发异常,则该断言将失败。

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

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

  assertTrue(e instanceof IndexOutOfBoundsException);
}

BDD样式解决方案: JUnit 4 + 捕获异常 + AssertJ

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

@Test
public void testFooThrowsIndexOutOfBoundsException() {

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

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

}

依存关系

eu.codearte.catch-exception:catch-exception:2.0

使用AssertJ断言,可以与 JUnit 一起使用:

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

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

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

它比@Test(expected=IndexOutOfBoundsException.class)更好,因为它可以保证测试中的预期行引发异常,并使您可以更轻松地检查有关异常的更多详细信息,例如消息:

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

Maven / Gradle 说明在这里。