In Java how can I validate a thrown exception with JUnit?
在为Java API编写单元测试时,可能会出现需要对异常执行更详细验证的情况。即 比JUnit提供的@test注释提供的更多。
例如,考虑一个应该从其他接口捕获异常的类,包装该异常并抛出包装的异常。 您可能需要验证:
这里的要点是,您希望在单元测试中对异常进行额外验证(而不是关于是否应该验证异常消息等事项的争论)。
对此有什么好的方法?
根据你的答案,这是一个很好的方法。除此之外:
您可以将函数
带注释的方法如下所示:
1 2 3 4 5 | @Test @ExpectedException(class=WrapperException.class, message="Exception Message", causeException) public void testAnExceptionWrappingFunction() { //whatever you test } |
这种方式更具可读性,但它的方法完全相同。
另一个原因是:我喜欢Annotations :)
在JUnit 4中,可以使用ExpectedException规则轻松完成。
以下是javadocs的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // These tests all pass. public static class HasExpectedException { @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void throwsNothing() { // no exception expected, none thrown: passes. } @Test public void throwsNullPointerException() { thrown.expect(NullPointerException.class); throw new NullPointerException(); } @Test public void throwsNullPointerExceptionWithMessage() { thrown.expect(NullPointerException.class); thrown.expectMessage("happened?"); thrown.expectMessage(startsWith("What")); throw new NullPointerException("What happened?"); } } |
看看提出的答案,你真的可以感受到Java中没有闭包的痛苦。恕我直言,最可读的解决方案是你好老尝试赶上。
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Test public void test() { ... ... try { ... fail("No exception caught :("); } catch (RuntimeException ex) { assertEquals(Whatever.class, ex.getCause().getClass()); assertEquals("Message", ex.getMessage()); } } |
对于JUNIT 3.x
1 2 3 4 5 6 7 8 9 10 | public void test(){ boolean thrown = false; try{ mightThrowEx(); } catch ( Surprise expected ){ thrown = true; assertEquals("message", expected.getMessage()); } assertTrue(thrown ); } |
在这篇文章之前,我通过这样做完成了我的异常验证:
1 2 3 4 5 6 | try { myObject.doThings(); fail("Should've thrown SomeException!"); } catch (SomeException e) { assertEquals("something", e.getSomething()); } |
我花了一些时间考虑这个问题,并想出了以下内容(Java5,JUnit 3.x):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | // Functor interface for exception assertion. public interface AssertionContainer<T extends Throwable> { void invoke() throws T; void validate(T throwable); Class< T > getType(); } // Actual assertion method. public <T extends Throwable> void assertThrowsException(AssertionContainer< T > functor) { try { functor.invoke(); fail("Should've thrown"+functor.getType()+"!"); } catch (Throwable exc) { assertSame("Thrown exception was of the wrong type! Expected"+functor.getClass()+", actual"+exc.getType(), exc.getClass(), functor.getType()); functor.validate((T) exc); } } // Example implementation for servlet I used to actually test this. It was an inner class, actually. AssertionContainer<ServletException> functor = new AssertionContainer<ServletException>() { public void invoke() throws ServletException { servlet.getRequiredParameter(request,"some_param"); } public void validate(ServletException e) { assertEquals("Parameter "some_param" wasn't found!", e.getMessage()); } public Class<ServletException> getType() { return ServletException.class; } } // And this is how it's used. assertThrowsException(functor); |
看着这两个我无法决定哪一个我更喜欢。我想这是其中一个问题,其中实现目标(在我的情况下,使用functor参数的断言方法)从长远来看是不值得的,因为这些6+代码断言尝试要容易得多..catch块。
再说一次,也许我在星期五晚上解决问题的10分钟结果并不是最聪明的方法。
@akuhn:
即使没有闭包,我们也可以获得更易读的解决方案(使用catch-exception):
1 2 3 4 5 6 7 8 9 | import static com.googlecode.catchexception.CatchException.*; public void test() { ... ... catchException(nastyBoy).doNastyStuff(); assertTrue(caughtException() instanceof WhateverException); assertEquals("Message", caughtException().getMessage()); } |
我做了一件很简单的事
1 2 3 4 5 6 7 8 | testBla(){ try { someFailingMethod() fail(); //method provided by junit } catch(Exception e) { //do nothing } } |
以下帮助方法(改编自此博客文章)可以解决问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | /** * Run a test body expecting an exception of the * given class and with the given message. * * @param test To be executed and is expected to throw the exception. * @param expectedException The type of the expected exception. * @param expectedMessage If not null, should be the message of the expected exception. * @param expectedCause If not null, should be the same as the cause of the received exception. */ public static void expectException( Runnable test, Class<? extends Throwable> expectedException, String expectedMessage, Throwable expectedCause) { try { test.run(); } catch (Exception ex) { assertSame(expectedException, ex.getClass()); if (expectedMessage != null) { assertEquals(expectedMessage, ex.getMessage()); } if (expectedCause != null) { assertSame(expectedCause, ex.getCause()); } return; } fail("Didn't find expected exception of type" + expectedException.getName()); } |
然后,测试代码可以如下调用:
1 2 3 4 5 6 7 8 9 10 | TestHelper.expectException( new Runnable() { public void run() { classInstanceBeingTested.methodThatThrows(); } }, WrapperException.class, "Exception Message", causeException ); |
对于JUnit 5,它更容易:
1 2 3 4 5 6 7 8 9 10 | @Test void testAppleIsSweetAndRed() throws Exception { IllegalArgumentException ex = assertThrows( IllegalArgumentException.class, () -> testClass.appleIsSweetAndRed("orange","red","sweet")); assertEquals("this is the exception message", ex.getMessage()); assertEquals(NullPointerException.class, ex.getCause().getClass()); } |
通过返回异常对象本身,
我做了一个类似于其他帖子的帮手:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | public class ExpectExceptionsExecutor { private ExpectExceptionsExecutor() { } public static void execute(ExpectExceptionsTemplate e) { Class<? extends Throwable> aClass = e.getExpectedException(); try { Method method = ExpectExceptionsTemplate.class.getMethod("doInttemplate"); method.invoke(e); } catch (NoSuchMethodException e1) { throw new RuntimeException(); } catch (InvocationTargetException e1) { Throwable throwable = e1.getTargetException(); if (!aClass.isAssignableFrom(throwable.getClass())) { // assert false fail("Exception isn't the one expected"); } else { assertTrue("Exception captured", true); return; } ; } catch (IllegalAccessException e1) { throw new RuntimeException(); } fail("No exception has been thrown"); } } |
以及客户端应该实现的模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public interface ExpectExceptionsTemplate<T extends Throwable> { /** * Specify the type of exception that doInttemplate is expected to throw * @return */ Class< T > getExpectedException(); /** * Execute risky code inside this method * TODO specify expected exception using an annotation */ public void doInttemplate(); } |
客户端代码将是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Test public void myTest() throws Exception { ExpectExceptionsExecutor.execute(new ExpectExceptionsTemplate() { @Override public Class getExpectedException() { return IllegalArgumentException.class; } @Override public void doInttemplate() { riskyMethod.doSomething(null); } }); } |
它看起来真的很冗长但如果你使用具有良好自动完成功能的IDE,你只需要编写异常类型和正在测试的实际代码。 (其余的将由IDE完成:D)