2009-04-24 65 views
27

在爲Java API編寫單元測試時,可能會出現您想要執行更多詳細的異常驗證的情況。即比由JUnit提供的@test註釋提供的更多。在Java中,我如何使用JUnit驗證拋出的異常?

例如,考慮一個類應該從其他接口捕獲一個異常,包裝該異常並拋出包裝的異常。您可能需要驗證:

  1. 引發包裝異常的確切方法調用。
  2. 包裝異常具有原始異常作爲其原因。
  3. 包裝異常的消息。

這裏主要的一點是,你要在一個單元測試(而不是有關是否應該驗證像異常信息的事情辯論)異常的PERF的額外的驗證。

這是什麼方法?

+0

如果您能夠將最合適的答案作爲正確的答案,我將不勝感激。 – guerda 2009-10-22 11:32:07

+1

你不應該關心*哪個*方法拋出異常:這是一個實現細節。 – Raedwald 2015-12-18 19:48:10

回答

20

在JUnit 4可以使用ExpectedException規則很容易完成。

這裏是例如從的javadoc:

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

以下輔助方法(改編自this博客文章)的伎倆:

/** 
* 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()); 
} 

測試代碼然後可以調用此如下:

TestHelper.expectException(
     new Runnable() { 
      public void run() { 
       classInstanceBeingTested.methodThatThrows(); 
      } 
     }, 
     WrapperException.class, 
     "Exception Message", 
     causeException 
); 
+0

有什麼辦法可以壓縮Java中的內聯類/方法嗎? – Iain 2009-04-24 14:19:07

24

your answer提供,這是一個好的方法。除此之外:

您可以將函數expectException包裝爲一個新的註釋,稱爲ExpectedException
帶註釋的方法是這樣的:

@Test 
@ExpectedException(class=WrapperException.class, message="Exception Message", causeException) 
public void testAnExceptionWrappingFunction() { 
    //whatever you test 
} 

這種方式會更可讀的,但它是完全一樣的方法。

另一個原因是:我喜歡註解:)

+2

以這種方式,您必須擴展測試運行器以考慮@ExpectedException – dfa 2009-04-24 12:56:45

+1

這絕對是一個很好的答案。非常可讀,其中IMO是編寫良好的代碼的屬性之一 – 2009-04-24 13:12:21

+0

愛迪生:我還沒有寫出註解;-) – guerda 2009-04-24 13:23:47

11

針對JUnit 3.x的

public void test(){ 
    boolean thrown = false; 
    try{ 
     mightThrowEx(); 
    } catch (Surprise expected){ 
     thrown = true; 
     assertEquals("message", expected.getMessage()); 
    } 
    assertTrue(thrown); 
} 
+0

甜....... !!! – Rachel 2013-06-19 20:28:05

5

直到這個職位,我已經做這做了我的異常驗證:

try { 
    myObject.doThings(); 
    fail("Should've thrown SomeException!"); 
} catch (SomeException e) { 
    assertEquals("something", e.getSomething()); 
} 

我花了一些時間思考問題,並提出了以下內容(Java5,JUnit 3.x):

// 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分鐘解決問題的結果並不是最聰明的方法。

2

我做了很簡單的

testBla(){ 
    try { 
     someFailingMethod() 
     fail(); //method provided by junit 
    } catch(Exception e) { 
      //do nothing 
    } 
} 
18

望着提出的答案,你可以真正感受到在Java中沒有倒閉之痛。恕我直言,最可讀的解決方案是你好老嘗試趕上。

@Test 
public void test() { 
    ... 
    ... 
    try { 
     ... 
     fail("No exception caught :("); 
    } 
    catch (RuntimeException ex) { 
     assertEquals(Whatever.class, ex.getCause().getClass()); 
     assertEquals("Message", ex.getMessage()); 
    } 
} 
0

我提出了類似的其他發佈者的輔助:

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


} 

而且模板中的客戶端應該實現

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

} 

而且客戶端代碼會是這樣的:

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

4

@akuhn:

即使沒有倒閉,我們可以(用catch-exception)得到更可讀的解決方案:

import static com.googlecode.catchexception.CatchException.*; 

public void test() { 
    ... 
    ... 
    catchException(nastyBoy).doNastyStuff(); 
    assertTrue(caughtException() instanceof WhateverException); 
    assertEquals("Message", caughtException().getMessage()); 
} 
0

對於JUnit的5這是很容易:

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

通過返回的異常對象本身,assertThrows()讓你測試對於每一個方面你拋出異常。