你如何断言在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之前的版本:我会推荐老好的
try
–catch
块。 -
后JDK8:使用AssertJ或自定义lambdaexpression式来声明exception行为。
长话短说
可以自己写一个try
– catch
块或使用JUnit工具( @Test(expected = ...)
或者@Rule ExpectedException
JUnit规则特性)。
但是这些方式并不是那么优雅,不能很好地与其他工具混合良好的可读性 。
-
try
–catch
块必须围绕被testing的行为编写块,然后在catch块中写入断言,这可能很好,但很多人发现这种风格会中断读取testing的stream程。 你也需要在try
块的末尾写一个Assert.fail
,否则testing可能会错过断言的一边; PMD , findbugs或Sonar会发现这样的问题。 -
@Test(expected = ...)
特性很有趣,因为你可以编写更less的代码,然后编写这个testing应该不太容易出现编码错误。 但是这种方法缺乏一些领域。- 如果testing需要检查exception(如原因或消息)上的其他内容(好的exception消息非常重要,但有一个精确的exceptiontypes可能不够)。
-
另外,随着期望被放在方法中,取决于如何写testing代码,然后错误的部分testing代码可以抛出exception,导致误报testing,我不知道PMD , findbugs或声纳会给暗示这样的代码。
@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 }
-
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上的此评论问题。
所以上面的这些选项都有其注意事项,显然不能免除编码器错误。
-
有一个项目,我觉得创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工具,我会推荐
try
–catch
块的旧好方法,至less到JDK7。 而对于JDK 8用户,您可能更喜欢使用AssertJ,因为它提供的可能不仅仅是声明exception。 -
-
随着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"); }
-
随着对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那样链接断言。另外,如果你记得
Matcher
或Assert
名字冲突,准备与Assertions
发生同样的冲突。
今天(2017-03-03) AssertJ的易用性,可发现的API,快速的开发速度以及事实上的testing依赖性是JDK8的最佳解决scheme,无论testing框架如何(JUnit或不),以前的JDK应该依靠try
– catch
块,即使他们觉得笨重。
这个答案已经从另一个不具有相同可见性的问题复制,我是同一个作者。
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件事情。
- 抛出的exception的types
- exception消息
- 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:
- Built into JUnit
- 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
- Concise
- Allows your tests to follow Arrange-Act-Assert
- You can precisely indicate what code you are expecting to throw the exception
- You don't need to list the expected exception in the
throws
clause - 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
- Annotate the test with the exception which is thrown by the method. Something like this
@Test(expected = IndexOutOfBoundsException.class)
-
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); }