你如何断言在JUnit 4testing中引发了某个exception?

我怎样才能使用JUnit4惯用testing一些代码抛出exception?

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

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

编辑现在JUnit5已经发布了,最好的select是使用Assertions.assertThrows() (见我的其他答案 )。

如果您尚未迁移到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 ,testing将会失败。

详情请参阅这篇文章

请小心使用预期的exception,因为它只声明方法抛出exception,而不是testing中的特定代码行

我倾向于使用它来testing参数validation,因为这样的方法通常非常简单,但是更复杂的testing可能更适合于:

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

应用判断。

如前所述,在JUnit中有很多处理exception的方法。 但是对于Java 8,还有另外一个:使用Lambdaexpression式。 使用Lambdaexpression式,我们可以实现这样的语法:

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

assertThrown接受一个函数接口,其实例可以用lambdaexpression式,方法引用或构造函数引用来创build。 assertThrown接受该接口将期望并准备好处理exception。

这是相对简单而强大的技术。

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

源代码可以在这里find: https : //github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8

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

在testing中,有三种方法来testingexception。

  • 使用Test annonation的可选'expected'属性

     @Test(expected = IndexOutOfBoundsException.class) public void testFooThrowsIndexOutOfBoundsException() { foo.doStuff(); } 
  • 使用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. } } 
  • 所以

    • 当你只想testingexceptiontypes时使用的第一种方法
    • 当你想要进一步testingexception消息时使用的第二和第三种方法
    • 如果你使用junit 3,那么第三个是首选。
  • 欲了解更多信息,你可以阅读这个文件的细节。

TL;博士

  • JDK8之前的版本:我会推荐老好的trycatch块。

  • 后JDK8:使用AssertJ或自定义lambdaexpression式来声明exception行为。

长话短说

可以自己写一个trycatch块或使用JUnit工具( @Test(expected = ...)或者@Rule ExpectedException JUnit规则特性)。

但是这些方式并不是那么优雅,不能很好与其他工具混合良好的可读性

  1. trycatch块必须围绕被testing的行为编写块,然后在catch块中写入断言,这可能很好,但很多人发现这种风格会中断读取testing的stream程。 你也需要在try块的末尾写一个Assert.fail ,否则testing可能会错过断言的一边; PMDfindbugsSonar会发现这样的问题。

  2. @Test(expected = ...)特性很有趣,因为你可以编写更less的代码,然后编写这个testing应该不太容易出现编码错误。 但是这种方法缺乏一些领域。

    • 如果testing需要检查exception(如原因或消息)上的其他内容(好的exception消息非常重要,但有一个精确的exceptiontypes可能不够)。
    • 另外,随着期望被放在方法中,取决于如何写testing代码,然后错误的部分testing代码可以抛出exception,导致误报testing,我不知道PMDfindbugs声纳会给暗示这样的代码。

       @Test(expected = WantedException.class) public void call2_should_throw_a_WantedException__not_call1() { // init tested tested.call1(); // may throw a WantedException // call to be actually tested tested.call2(); // the call that is supposed to raise an exception } 
  3. ExpectedException规则也是一个尝试修正以前的注意事项,但是使用expectation样式感觉有些尴尬, EasyMock用户非常清楚这种样式。 对于某些人来说,这可能会很方便,但是如果遵循行为驱动开发 (BDD)或排列行为断言 (AAA)原则,则ExpectedException规则将不适合这些写作风格。 除此之外,它可能会受到与@Test方式相同的问题,具体取决于您所期望的位置。

     @Rule ExpectedException thrown = ExpectedException.none() @Test public void call2_should_throw_a_WantedException__not_call1() { // expectations thrown.expect(WantedException.class); thrown.expectMessage("boom"); // init tested tested.call1(); // may throw a WantedException // call to be actually tested tested.call2(); // the call that is supposed to raise an exception } 

    即使预期的exception被放在testing语句之前,如果testing遵循BDD或AAA,它也会中断您的阅读stream程。

    另请参阅ExpectedException作者的JUnit上的此评论问题。

所以上面的这些选项都有其注意事项,显然不能免除编码器错误。

  1. 有一个项目,我觉得创build这个答案看起来很有希望之后,这是个例外

    正如这个项目的描述所说的那样,它让一个编码人员用一连串的代码来捕捉这个exception,并为以后的断言提供这个exception。 你可以使用任何断言库,如Hamcrest或AssertJ 。

    从主页上取得一个快速的例子:

     // given: an empty list List myList = new ArrayList(); // when: we try to get the first element of the list when(myList).get(1); // then: we expect an IndexOutOfBoundsException then(caughtException()) .isInstanceOf(IndexOutOfBoundsException.class) .hasMessage("Index: 1, Size: 0") .hasNoCause(); 

    正如你所看到的,代码是非常直接的,你可以在特定的行上捕获exception, then API就是使用AssertJ API的别名(类似于使用assertThat(ex).hasNoCause()... )。 在某个时候,项目依赖于FEST-声明AssertJ的祖先编辑:看来这个项目正在酝酿一个Java 8 Lambdas的支持。

    目前这个库有两个缺点:

    • 在撰写本文时,值得一提的是,这个库基于Mockito 1.x,因为它创build了场景中被testing对象的模拟。 由于Mockito还没有更新, 这个库不能用于最终的类或最终的方法 。 即使它是基于当前版本的mockito 2,这也需要声明一个全球模拟器( inline-mock-maker模拟器),这可能不是你想要的,因为这个模拟器与普通的模拟器有不同的缺点。

    • 它需要另一个testing依赖项。

    这些问题将不会适用,一旦库将支持lambdas,但function将被复制的AssertJ工具集。

    考虑到所有的问题,如果你不想使用捕获exception工具,我会推荐trycatch块的旧好方法,至less到JDK7。 而对于JDK 8用户,您可能更喜欢使用AssertJ,因为它提供的可能不仅仅是声明exception。

  2. 随着JDK8,lambda进入testing场景,并且已经certificate是一个有效的方式来断言exception的行为。 AssertJ已经更新,提供了一个很好的stream畅的API来声明exception的行为。

    并用AssertJ进行样本testing:

     @Test public void test_exception_approach_1() { ... assertThatExceptionOfType(IOException.class) .isThrownBy(() -> someBadIOOperation()) .withMessage("boom!"); } @Test public void test_exception_approach_2() { ... assertThatThrownBy(() -> someBadIOOperation()) .isInstanceOf(Exception.class) .hasMessageContaining("boom"); } @Test public void test_exception_approach_3() { ... // when Throwable thrown = catchThrowable(() -> someBadIOOperation()); // then assertThat(thrown).isInstanceOf(Exception.class) .hasMessageContaining("boom"); } 
  3. 随着对JUnit 5的几乎完全重写,断言已经有所改进 ,它们可能被certificate是一种开箱即用的方法,能够正确地exception断言。 但实际上断言API仍然有点差,除了assertThrows外没有任何东西。

     @Test @DisplayName("throws EmptyStackException when peeked") void throwsExceptionWhenPeeked() { Throwable t = assertThrows(EmptyStackException.class, () -> stack.peek()); Assertions.assertEquals("...", t.getMessage()); } 

    正如你注意到的, assertEquals仍然返回void ,因此不允许像AssertJ那样链接断言。

    另外,如果你记得MatcherAssert名字冲突,准备与Assertions发生同样的冲突。

今天(2017-03-03) AssertJ的易用性,可发现的API,快速的开发速度以及事实上的testing依赖性是JDK8的最佳解决scheme,无论testing框架如何(JUnit或不),以前的JDK应该依靠trycatch块,即使他们觉得笨重。

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

BDD风格解决scheme: JUnit 4 + Catch Exception + AssertJ

 @Test public void testFooThrowsIndexOutOfBoundsException() { when(foo).doStuff(); then(caughtException()).isInstanceOf(IndexOutOfBoundsException.class); } 

源代码

依赖

 eu.codearte.catch-exception:catch-exception:1.3.3 

为了解决同样的问题,我build立了一个小项目: http : //code.google.com/p/catch-exception/

使用这个小助手,你会写

 verifyException(foo, IndexOutOfBoundsException.class).doStuff(); 

这不如JUnit 4.7的ExpectedException规则冗长。 与skaffman提供的解决scheme相比,您可以指定您希望发生exception的代码行。 我希望这有帮助。

怎么样:抓住一个非常普遍的exception,确保它赶出catch块,然后断言exception的类是你所期望的。 如果a)exception的types错误(例如,如果你有一个空指针),并且b)这个exception没有被抛出,那么这个断言将会失败。

 public void testFooThrowsIndexOutOfBoundsException() { Throwable e = null; try { foo.doStuff(); } catch (Throwable ex) { e = ex; } assertTrue(e instanceof IndexOutOfBoundsException); } 

使用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)要好,因为它保证testing中预期的行会抛出exception,并让你更容易地检查exception的更多细节,例如message,

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

Maven / Gradle说明在这里。

你也可以这样做:

 @Test public void testFooThrowsIndexOutOfBoundsException() { try { foo.doStuff(); assert false; } catch (IndexOutOfBoundsException e) { assert true; } } 

更新: JUnit5对exceptiontesting有一个改进: assertThrows

以下示例来自: Junit 5用户指南

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

原来使用JUnit 4的asnwer。

有几种方法来testing抛出exception。 我也在我的post中讨论了如何使用JUnit编写出色的unit testing

设置expected参数@Test(expected = FileNotFoundException.class)

 @Test(expected = FileNotFoundException.class) public void testReadFile() { myClass.readFile("test.txt"); } 

使用try catch

 public void testReadFile() { try { myClass.readFile("test.txt"); fail("Expected a FileNotFoundException to be thrown"); } catch (FileNotFoundException e) { assertThat(e.getMessage(), is("The file test.txt does not exist!")); } } 

使用ExpectedException规则进行testing。

 @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void testReadFile() throws FileNotFoundException { thrown.expect(FileNotFoundException.class); thrown.expectMessage(startsWith("The file test.txt")); myClass.readFile("test.txt"); } 

您可以阅读更多关于在JUnit4 wiki中进行exceptiontesting以进行exceptiontesting和bad.robot – 期望exceptionJUnit规则 。

恕我直言,在JUnit中检查exception的最好方法是try / catch / fail / assert模式:

 // this try block should be as small as possible, // as you want to make sure you only catch exceptions from your code try { sut.doThing(); fail(); // fail if this does not throw any exception } catch(MyException e) { // only catch the exception you expect, // otherwise you may catch an exception for a dependency unexpectedly // a strong assertion on the message, // in case the exception comes from anywhere an unexpected line of code, // especially important if your checking IllegalArgumentExceptions assertEquals("the message I get", e.getMessage()); } 

assertThat(e.getMessage(), containsString("the message");对某些人来说可能有点强大,所以assertThat(e.getMessage(), containsString("the message");可能是可取的。

JUnit 5解决scheme

 @Test void testFooThrowsIndexOutOfBoundsException() { Throwable exception = expectThrows( IndexOutOfBoundsException.class, foo::doStuff ); assertEquals( "some message", exception.getMessage() ); } 

更多有关JUnit 5的信息http://junit.org/junit5/docs/current/user-guide/#writing-tests-assertions

我在这里尝试了很多方法,但是它们要么复杂,要么不完全符合我的要求。 事实上,人们可以很简单地写一个帮手方法:

 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; 和最后的class级一起工作得很好。

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

在我的情况下,我总是从db获得RuntimeException,但消息不同。 而exception则需要分别处理。 这是我如何testing它:

 @Test public void testThrowsExceptionWhenWrongSku() { // Given String articleSimpleSku = "999-999"; int amountOfTransactions = 1; Exception exception = null; // When try { createNInboundTransactionsForSku(amountOfTransactions, articleSimpleSku); } catch (RuntimeException e) { exception = e; } // Then shouldValidateThrowsExceptionWithMessage(exception, MESSAGE_NON_EXISTENT_SKU); } private void shouldValidateThrowsExceptionWithMessage(final Exception e, final String message) { assertNotNull(e); assertTrue(e.getMessage().contains(message)); } 

Java 8解决scheme

如果您想要一个解决scheme:

  • 使用Java 8 lambda
  • 依赖于任何JUnit魔法
  • 断言特定的一段代码抛出了一个特定的exception
  • 产生实际的exception,以便您可以进一步检查它

这是我写的一个实用函数:

 public final <T extends Throwable> T expectException( Class<T> exceptionClass, Runnable runnable ) { try { runnable.run(); } catch( Throwable throwable ) { if( throwable instanceof AssertionError && throwable.getCause() != null ) throwable = throwable.getCause(); //allows "assert x != null : new IllegalArgumentException();" assert exceptionClass.isInstance( throwable ) : throwable; //exception of the wrong kind was thrown. assert throwable.getClass() == exceptionClass : throwable; //exception thrown was a subclass, but not the exact class, expected. @SuppressWarnings( "unchecked" ) T result = (T)throwable; return result; } assert false; //expected exception was not thrown. return null; //to keep the compiler happy. } 

( 取自我的博客 )

使用它如下:

 @Test public void testThrows() { RuntimeException e = expectException( RuntimeException.class, () -> { throw new RuntimeException( "fail!" ); } ); assert e.getMessage().equals( "fail!" ); } 

在JUnit 4或更高版本中,您可以按如下方式testingexception

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

这提供了很多可以用来改进我们的JUnittesting的function。
如果你看到下面的例子,我正在testingexception的3件事情。

  1. 抛出的exception的types
  2. exception消息
  3. exception的原因

 public class MyTest { @Rule public ExpectedException exceptions = ExpectedException.none(); ClassUnderTest classUnderTest; @Before public void setUp() throws Exception { classUnderTest = new ClassUnderTest(); } @Test public void testAppleisSweetAndRed() throws Exception { exceptions.expect(Exception.class); exceptions.expectMessage("this is the exception message"); exceptions.expectCause(Matchers.<Throwable>equalTo(exceptionCause)); classUnderTest.methodUnderTest("param1", "param2"); } } 

在必须返回exception的方法之后,我们可以使用断言失败:

 try{ methodThatThrowMyException(); Assert.fail("MyException is not thrown !"); } catch (MyException ex) { } catch (Exception ex){ Assert.fail("An exception other than MyException is thrown !"); } 

只需制作一个可以closures和打开的匹配器,就像这样:

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

要使用它:

添加public ExpectedException exception = ExpectedException.none(); , 然后:

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

除了NamShubWriter的说法,请确保:

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

要这样做:

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

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

举个例子,你要为下面提到的代码片段写Junit

 public int divideByZeroDemo(int a,int b){ return a/b; } public void exceptionWithMessage(String [] arr){ throw new ArrayIndexOutOfBoundsException("Array is out of bound"); } 

The above code is to test for some unknown exception that may occur and the below one is to assert some exception with custom message.

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

Now that JUnit 5 has released, the best option is to use Assertions.assertThrows() (see the Junit 5 User Guide ).

Here is an example that verifies an exception is thrown, and uses Truth to make assertions on the exception message:

 public class FooTest { @Test public void doStuffThrowsIndexOutOfBoundsException() { Foo foo = new Foo(); IndexOutOfBoundsException e = assertThrows( IndexOutOfBoundsException.class, foo::doStuff); assertThat(e).hasMessageThat().contains("woops!"); } } 

The advantages over the approaches in the other answers are:

  1. Built into JUnit
  2. You get a useful exception message if the code in the lambda doesn't throw an exception, and a stacktrace if it throws a different exception
  3. Concise
  4. Allows your tests to follow Arrange-Act-Assert
  5. You can precisely indicate what code you are expecting to throw the exception
  6. You don't need to list the expected exception in the throws clause
  7. You can use the assertion framework of your choice to make assertions about the caught exception

A similar method will be added to org.junit Assert in JUnit 4.13.

With Java 8 you can create a method taking a code to check and expected exception as parameters:

 private void expectException(Runnable r, Class<?> clazz) { try { r.run(); fail("Expected: " + clazz.getSimpleName() + " but not thrown"); } catch (Exception e) { if (!clazz.isInstance(e)) fail("Expected: " + clazz.getSimpleName() + " but " + e.getClass().getSimpleName() + " found", e); } } 

and then inside your test:

 expectException(() -> list.sublist(0, 2).get(2), IndexOutOfBoundsException.class); 

优点:

  • not relying on any library
  • localised check – more precise and allows to have multiple assertions like this within one test if needed
  • 使用方便

My solution using Java 8 lambdas:

 public static <T extends Throwable> T assertThrows(Class<T> expected, ThrowingRunnable action) throws Throwable { try { action.run(); Assert.fail("Did not throw expected " + expected.getSimpleName()); return null; // never actually } catch (Throwable actual) { if (!expected.isAssignableFrom(actual.getClass())) { // runtime '!(actual instanceof expected)' System.err.println("Threw " + actual.getClass().getSimpleName() + ", which is not a subtype of expected " + expected.getSimpleName()); throw actual; // throw the unexpected Throwable for maximum transparency } else { return (T) actual; // return the expected Throwable for further examination } } } 

You have to define a FunctionalInterface, because Runnable doesn't declare the required throws .

 @FunctionalInterface public interface ThrowingRunnable { void run() throws Throwable; } 

The method can be used as follows:

 class CustomException extends Exception { public final String message; public CustomException(final String message) { this.message = message;} } CustomException e = assertThrows(CustomException.class, () -> { throw new CustomException("Lorem Ipsum"); }); assertEquals("Lorem Ipsum", e.message); 

There are two ways of writing test case

  1. Annotate the test with the exception which is thrown by the method. Something like this @Test(expected = IndexOutOfBoundsException.class)
  2. You can simply catch the exception in the test class using the try catch block and assert on the message that is thrown from the method in test class.

     try{ } catch(exception to be thrown from method e) { assertEquals("message", e.getmessage()); } 

I hope this answers your query Happy learning…

I wanted to comment with my solution to this problem, which avoided needing any of the exception related JUnit code.

I used assertTrue(boolean) combined with try/catch to look for my expected exception to be thrown. 这是一个例子:

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