java - assertthrows - junit4 assert throws




你如何斷言在JUnit 4測試中引發了某種異常? (20)

我如何通過慣用方式使用JUnit4來測試某些代碼是否會拋出異常?

雖然我當然可以做這樣的事情:

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

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

  assertTrue(thrown);
}

我記得有一個註釋或一個Assert.xyz或者其他一些遠遠不夠靈活的東西 ,而且在這種情況下JUnit的精神遠不止於此。


Java 8解決方案

如果您想要一個解決方案:

  • 使用Java 8 lambda
  • 依賴於任何JUnit魔法
  • 允許您在單個測試方法中檢查多個異常
  • 在你的測試方法中檢查一組特定的行引發的異常,而不是整個測試方法中的任何未知行
  • 產生拋出的實際異常對象,以便您可以進一步檢查它

這是我寫的一個實用函數:

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 5解決方案

@Test
void testFooThrowsIndexOutOfBoundsException() {    
  Throwable exception = expectThrows( IndexOutOfBoundsException.class, foo::doStuff );

  assertEquals( "some message", exception.getMessage() );
}

有關JUnit 5的更多信息,請訪問improved




更新: JUnit5對異常測試有一個改進: assertThrows

以下示例來自: Junit 5用戶指南

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

使用JUnit 4的原始答案。

有幾種方法可以測試引發異常。 我也在我的文章中討論瞭如何使用JUnit編寫出色的單元測試

設置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規則進行測試。

@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中異常測試的異常測試bad.robot - 期望異常JUnit規則


編輯現在JUnit5已經發布,最好的選擇是使用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 ,測試將失敗。

詳情請參閱這篇文章


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

我使用assertTrue(boolean)與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);
}

Junit4 solution with Java8 is to use this function:

public Throwable assertThrows(Class<? extends Throwable> expectedException, java.util.concurrent.Callable<?> funky) {
    try {
        funky.call();
    } catch (Throwable e) {
        if (expectedException.isInstance(e)) {
            return e;
        }
        throw new AssertionError(
                String.format("Expected [%s] to be thrown, but was [%s]", expectedException, e));
    }
    throw new AssertionError(
            String.format("Expected [%s] to be thrown, but nothing was thrown.", expectedException));
}

Usage is then:

    assertThrows(ValidationException.class,
            () -> finalObject.checkSomething(null));

請注意,唯一的限制是final在lambda表達式中使用對象引用。該解決方案允許繼續測試斷言,而不是期望在方法級使用@Test(expected = IndexOutOfBoundsException.class)解決方案。


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

Take for example, you want to write Junit for below mentioned code fragment

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

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...


We can use an assertion fail after the method that must return an exception:

try{
   methodThatThrowMyException();
   Assert.fail("MyException is not thrown !");
} catch (final Exception exception) {
   // Verify if the thrown exception is instance of MyException, otherwise throws an assert failure
   assertTrue(exception instanceof MyException, "An exception other than MyException is thrown !");
   // In case of verifying the error message
   MyException myException = (MyException) exception;
   assertEquals("EXPECTED ERROR MESSAGE", myException.getMessage());
}

你也可以這樣做:

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

使用可與JUnit一起使用的AssertJ斷言:

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)要好,因為它確保測試中預期的行會拋出異常,並讓您更輕鬆地檢查有關異常的更多詳細信息,如消息:

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

Maven / Gradle說明。


在我的情況下,我總是從db獲得RuntimeException,但消息不同。 而異常則需要分別處理。 以下是我對它的測試:

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

在測試中,有三種方法來測試異常。

  • 使用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.
        }
    }
    
  • 所以

    • 當您只想測試異常類型時使用的第一種方法
    • 進一步想要測試異常消息時使用的第二種和第三種方法
    • 如果你使用junit 3,那麼第三個是首選。
  • 欲了解更多信息,你可以閱讀github.com/junit-team/junit/wiki/Exception-testing的細節。


小心使用預期的異常,因為它只聲明該方法拋出該異常,而不是測試中的特定代碼行

我傾向於使用它來測試參數驗證,因為這些方法通常非常簡單,但更複雜的測試可能更適合:

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

應用判斷。


怎麼樣:抓住一個非常普遍的異常,確保它使它脫離catch塊,然後聲明異常的類是你期望的。 如果a)異常是錯誤的類型(例如,如果你得到一個空指針)並且b)異常沒有被拋出,這個斷言將失敗。

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

  try {
    foo.doStuff();
  } catch (Throwable ex) {
    e = ex;
  }

  assertTrue(e instanceof IndexOutOfBoundsException);
}

我在這裡嘗試了很多方法,但它們要么複雜,要么不完全符合我的要求。 事實上,人們可以很簡單地編寫一個輔助方法:

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; 並在最終課程中工作得很好。


為了解決同樣的問題,我建立了一個小型項目: http://code.google.com/p/catch-exception/ : http://code.google.com/p/catch-exception/

使用這個小助手,你會寫

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

這不如JUnit 4.7的ExpectedException規則冗長。 與skaffman提供的解決方案相比,您可以指定您希望發生異常的代碼行。 我希望這有幫助。





assert