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

我当然可以这样做:

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

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

  assertTrue(thrown);
}

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


当前回答

我在这里尝试了许多方法,但它们要么很复杂,要么不太符合我的要求。事实上,可以非常简单地编写一个助手方法:

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;
    }
}

这样使用:

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

零依赖:无需mockito,无需powermock;在期末考试中表现很好。

其他回答

小心使用预期的异常,因为它只断言方法抛出了该异常,而不是测试中的特定代码行。

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

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

应用判断。

我想评论一下这个问题的解决方案,它避免了任何与异常相关的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);
}

这样如何:捕获一个非常普通的异常,确保它从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用户指南。

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

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

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

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

与其他答案中的方法相比,优势在于:

内置于JUnit如果lambda中的代码没有引发异常,则会得到一个有用的异常消息;如果它引发了不同的异常,则将得到一个堆栈跟踪简明的允许您的测试遵循排列动作断言您可以精确地指示希望抛出异常的代码您不需要在throws子句中列出预期的异常您可以使用自己选择的断言框架对捕获的异常进行断言

只需制作一个可以关闭和打开的Matcher,如下所示:

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

要使用它:

add public ExpectedException exception=ExpectedException.none();,那么:

ExceptionMatcher exMatch = new ExceptionMatcher(MyException.class);
exception.expect(exMatch);
someObject.somethingThatThrowsMyException();
exMatch.off();