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
JUnit 4支持這一點:
@Test(expected = IndexOutOfBoundsException.class)
public void testIndexOutOfBoundsException() {
ArrayList emptyList = new ArrayList();
Object o = emptyList.get(0);
}
參考: https://junit.org/junit4/faq.html#atests_7 : https://junit.org/junit4/faq.html#atests_7
JUnit內置了對此的支持,並具有“預期”屬性
更新: 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
- 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...
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");
在我的情況下,我總是從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提供的解決方案相比,您可以指定您希望發生異常的代碼行。 我希望這有幫助。