您如何断言在 JUnit 测试中抛出某个异常?

How do you assert that a certain exception is thrown in JUnit tests?

提问人:SCdF 提问时间:10/1/2008 最后编辑:Arun SudhakaranSCdF 更新时间:8/24/2023 访问量:1876106

问:

如何以惯用的方式使用 JUnit 来测试某些代码是否引发异常?

虽然我当然可以做这样的事情:

@Test
public void testFooThrowsIndexOutOfBoundsException() {

    boolean thrown = false;

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

    assertTrue(thrown);
}

我记得有一种注释或 Assert.xyz,或者对于这些情况,JUnit 的风格要少得多,而且更符合 JUnit 的精神。

java 异常 junit junit4 断言

评论

27赞 ZeroOne 1/17/2013
任何其他方法的问题在于,一旦抛出异常,它们总是会结束测试。另一方面,我通常仍然希望使用各种参数进行调用,以确保在抛出异常之前发生了某些事情(例如,使用正确的参数调用了记录器服务)。org.mockito.Mockito.verify
5赞 PhoneixS 2/19/2014
您可以在 JUnit wiki 页面 github.com/junit-team/junit/wiki/Exception-testing 中查看如何进行异常测试
6赞 tddmonkey 12/26/2014
@ZeroOne - 为此,我将有两个不同的测试 - 一个用于异常,另一个用于验证与您的模拟的交互。
0赞 Dilini Rajapaksha 5/2/2017
有一种方法可以用 JUnit 5 做到这一点,我已经在下面更新了我的答案。
0赞 Hitesh Garg 4/13/2021
下面是一个很好的例子,说明如何在 JUnit4 和 JUnit5 中断言异常被抛出

答:

46赞 Johan 10/1/2008 #1

怎么样:捕获一个非常通用的异常,确保它从捕获块中出来,然后断言异常的类是你所期望的。如果 a) 异常类型错误(例如,如果您得到的是 Null 指针),并且 b) 从未抛出异常,则此断言将失败。

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

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

  assertTrue(e instanceof IndexOutOfBoundsException);
}

评论

4赞 jontejj 3/15/2013
此外,当测试失败的那一天到来时,您不会在测试结果中看到哪种异常 ex。
1赞 Cypher 11/15/2018
这可以通过改变你最后的断言方式来改善。 将在测试失败时显示预期值和实际值。assertEquals(ExpectedException.class, e.getClass())
12赞 Mark Bessey 10/1/2008 #2

JUnit 对此有内置支持,并具有“预期”属性

2561赞 skaffman 10/1/2008 #3

这取决于 JUnit 版本和您使用的断言库。

最初的答案是:JUnit <= 4.12

    @Test(expected = IndexOutOfBoundsException.class)
    public void testIndexOutOfBoundsException() {

        ArrayList emptyList = new ArrayList();
        Object o = emptyList.get(0);

    }

尽管 answer 为 JUnit <= 4.12 提供了更多选项。

参考:

评论

78赞 Oh Chin Boon 6/27/2011
如果您只期望代码中的某处出现异常,而不是像这样的毯子,则这段代码将不起作用。
5赞 Artem Oboturov 4/28/2012
@skaffman 这不适用于 org.junit.experimental.theories.由 org.junit.experimental.theories.Theories 运行的 org.junit.experimental.theories.Theory
85赞 Kevin Wittek 1/22/2015
Roy Osherove 在《单元测试的艺术》一书中不鼓励进行这种异常测试,因为异常可能存在于测试内部的任何地方,而不仅仅是在被测试单元内部。
23赞 nickbdyer 5/3/2016
我不同意@Kiview/Roy Osherove的观点。在我看来,测试应该是针对行为,而不是实施。通过测试特定方法是否可能引发错误,您可以将测试直接绑定到实现。我认为,使用上述方法进行测试提供了更有价值的测试。我要补充的警告是,在这种情况下,我将测试自定义异常,以便我知道我得到了我真正想要的异常。
6赞 nickbdyer 5/4/2016
也不。我想测试班级的行为。重要的是,如果我试图检索不存在的东西,我会得到一个异常。数据结构是响应的事实是无关紧要的。如果我将来选择迁移到基元数组,那么我将不得不更改此测试实现。数据结构应该是隐藏的,以便测试可以专注于的行为。ArrayListget()
505赞 daveb 10/1/2008 #4

使用预期异常时要小心,因为它只断言该方法引发了异常,而不是测试中的特定代码行

我倾向于使用它来测试参数验证,因为这些方法通常非常简单,但更复杂的测试可能最好使用:

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

应用判断。

评论

111赞 Rodney Gitzel 10/7/2010
也许我是老派,但我仍然更喜欢这个。它还为我提供了一个测试异常本身的地方:有时我有某些值的 getter 异常,或者我可能只是在消息中查找特定值(例如,在消息“无法识别的代码 'xyz'”中查找“xyz”)。
3赞 Eddie 3/10/2011
我认为 NamshubWriter 的方法为您提供了两全其美的方法。
4赞 user1154664 10/10/2012
使用 ExpectedException,您可以为每个方法调用 N 个 exception.expect 进行测试,如下所示: exception.expect(IndexOutOfBoundsException.class);foo.doStuff1();异常.expect(IndexOutOfBoundsException.class);foo.doStuff2();异常.expect(IndexOutOfBoundsException.class);foo.doStuff3();
10赞 NamshubWriter 2/25/2014
@user1154664 实际上,你不能。使用 ExpectedException,您只能测试一个方法是否引发异常,因为当调用该方法时,测试将停止执行,因为它引发了预期的异常!
2赞 Dawood ibn Kareem 7/26/2014
你的第一句话不是真的。使用 时,通常要做的是将期望值设置在预期抛出异常的行之前。这样,如果较早的行引发异常,则不会触发规则,并且测试将失败。ExpectedException
1433赞 NamshubWriter 5/30/2010 #5

编辑:现在 JUnit 5 和 JUnit 4.13 已经发布,最好的选择是使用 Assertions.assertThrows() (对于 JUnit 5) 和 Assert.assertThrows() (对于 JUnit 4.13+)。有关详细信息,请参阅我的其他答案

如果您尚未迁移到 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)IndexOutOfBoundsExceptionfoo.doStuff()

有关详细信息,请参阅此文章

评论

17赞 bacar 7/6/2012
@skaffman - 如果我理解正确的话,看起来 exception.expect 只在一个测试中应用,而不是整个类。
5赞 Mohammad Jafar Mashhadi 6/29/2013
如果我们期望抛出的异常是已检查的异常,我们是否应该添加抛出或尝试捕获或以另一种方式测试这种情况?
5赞 Jason Thompson 1/17/2014
@MartinTrummer 任何代码都不应在 foo.doStuff() 之后运行,因为会抛出异常并退出该方法。无论如何,在预期异常之后使用代码(在 finally 中关闭资源除外)是没有帮助的,因为如果抛出异常,则永远不应该执行它。
9赞 Dawood ibn Kareem 7/26/2014
这是最好的方法。与 skaffman 的解决方案相比,这里有两个优点。首先,该类有匹配异常消息的方法,甚至可以编写自己的匹配器,该匹配器依赖于异常类。其次,您可以将期望设置在预期抛出异常的代码行之前 - 这意味着如果错误的代码行抛出异常,您的测试将失败;而 Skaffman 的解决方案无法做到这一点。ExpectedException
5赞 NamshubWriter 5/10/2015
@MJafarMash,如果选中了预期抛出的异常,则应将该异常添加到测试方法的 throws 子句中。每当测试声明为引发已检查异常的方法时,您都会执行相同的操作,即使该异常未在特定测试用例中触发。
33赞 rwitzel 10/28/2011 #6

为了解决同样的问题,我确实建立了一个小项目:http://code.google.com/p/catch-exception/

使用这个小帮手,你会写

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

这比 JUnit 4.7 的 ExpectedException 规则更详细。 与 skaffman 提供的解决方案相比,您可以指定在哪一行代码中预期异常。我希望这会有所帮助。

评论

0赞 Jason Thompson 1/18/2014
我也想过做这样的事情,但最终发现 ExpectedException 的真正力量在于,您不仅可以指定预期的异常,还可以指定异常的某些属性,例如预期原因或预期消息。
0赞 Tom 6/21/2014
我的猜测是,这个解决方案有一些与模拟相同的缺点?例如,如果 is 它会失败,因为您无法代理?foofinalfoo
0赞 rwitzel 1/16/2015
汤姆,如果 doStuff() 是接口的一部分,代理方法将起作用。否则这种方法会失败,你是对的。
12赞 Hugh Perkins 10/10/2012 #7

我在这里尝试了许多方法,但它们要么很复杂,要么不太符合我的要求。事实上,可以非常简单地编写一个辅助方法:

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;并且与期末课程配合得很好。

评论

0赞 grackkle 10/29/2014
有趣,但不适合 AAA(安排行为断言),您想在实际上不同的步骤中执行 Act 和 Assert 步骤。
1赞 Hakanai 2/2/2015
@bln-tom 从技术上讲,这是两个不同的步骤,它们只是不按这个顺序排列。;p
22赞 John Mikic 5/8/2013 #8

您还可以执行此操作:

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

评论

12赞 NamshubWriter 1/14/2015
在 JUnit 测试中,最好使用 ,而不是 ,以防万一您的测试在未启用断言的环境中运行。Assert.fail()assert
6赞 Tor P 6/6/2013 #9

只需制作一个可以关闭和打开的匹配器,如下所示:

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();
9赞 Macchiatow 7/2/2013 #10

就我而言,我总是从 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));
}

评论

1赞 Daniel Alder 12/18/2017
对于带有 的行,您应该插入} catch (fail("no exception thrown");
39赞 MariuszS 11/16/2013 #11

BDD的样式解决方案:JUnit 4 + Catch Exception + AssertJ

import static com.googlecode.catchexception.apis.BDDCatchException.*;

@Test
public void testFooThrowsIndexOutOfBoundsException() {

    when(() -> foo.doStuff());

    then(caughtException()).isInstanceOf(IndexOutOfBoundsException.class);

}

依赖

eu.codearte.catch-exception:catch-exception:2.0
223赞 Rafal Borowiec 7/8/2014 #12

如前所述,在 JUnit 中有很多方法可以处理异常。但是在 Java 8 中还有另一个:使用 Lambda 表达式。使用 Lambda 表达式,我们可以实现如下语法:

@Test
public void verifiesTypeAndMessage() {
    assertThrown(new DummyService()::someMethod)
            .isInstanceOf(RuntimeException.class)
            .hasMessage("Runtime exception occurred")
            .hasMessageStartingWith("Runtime")
            .hasMessageEndingWith("occurred")
            .hasMessageContaining("exception")
            .hasNoCause();
}

assertThrown 接受函数接口,可以使用 lambda 表达式、方法引用或构造函数引用创建其实例。assertThrown 接受该接口将期望并准备好处理异常。

这是一种相对简单但强大的技术。

看看这篇描述这种技术的博客文章:http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html

源代码可以在这里找到: https://github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8

披露:我是博客和项目的作者。

评论

2赞 Selwyn 3/31/2015
我喜欢这个解决方案,但我可以从 maven 存储库下载这个吗?
0赞 NamshubWriter 5/10/2015
@Airduster Maven 上可用的这个想法的一个实现是 stefanbirkner.github.io/vallado
6赞 NamshubWriter 7/21/2015
@CristianoFontes此 API 的更简单版本计划用于 JUnit 4.13。查看 github.com/junit-team/junit/commit/...
0赞 Andy 4/29/2017
@RafalBorowiec技术上,是 ,但这种方法同样适用于 lambda 表达式。new DummyService()::someMethodMethodHandle
0赞 Vadzim 12/19/2017
@NamshubWriter,似乎 junit 4.13 被放弃了,取而代之的是 junit 5:stackoverflow.com/questions/156503/......
6赞 Shessuky 3/9/2015 #13

我们可以在必须返回异常的方法之后使用断言失败:

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

评论

3赞 NamshubWriter 8/30/2015
如果抛出其他异常,第二个将吞噬堆栈跟踪,从而丢失有用的信息catch
15赞 Alex Collins 3/11/2015 #14

恕我直言,在 JUnit 中检查异常的最佳方法是 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()); 
}

对于某些人来说,这可能有点强,所以可能更可取。assertTrueassertThat(e.getMessage(), containsString("the message");

5赞 Srini 5/23/2015 #15

除了 NamShubWriter 所说的之外,请确保:

  • ExpectedException 实例是公共的(相关问题)
  • ExpectedException 不会在 @Before 方法中实例化。这篇文章清楚地解释了 JUnit 执行顺序的所有复杂性。

请勿这样做:

@Rule    
public ExpectedException expectedException;

@Before
public void setup()
{
    expectedException = ExpectedException.none();
}

最后,这篇博文清楚地说明了如何断言抛出某个异常。

240赞 walsh 8/5/2015 #16

在 JUNIT 中,有四种方法可以测试异常。

JUNIT5.x

  • 对于 JUNIT5.x,您可以按以下方式使用assertThrows

    @Test
    public void testFooThrowsIndexOutOfBoundsException() {
        Throwable exception = assertThrows(IndexOutOfBoundsException.class, () -> foo.doStuff());
        assertEquals("expected messages", exception.getMessage());
    }
    

朱尼特4.x

  • 对于 junit4.x,请使用测试 annonation 的可选“expected”属性

    @Test(expected = IndexOutOfBoundsException.class)
    public void testFooThrowsIndexOutOfBoundsException() {
        foo.doStuff();
    }
    
  • 对于 junit4.x,请使用 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();
        }
    }
    
  • 您还可以使用 JUn 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 5,那么你应该喜欢第一个
    • 当您只想测试异常类型时,使用第二种方式
    • 当您需要进一步测试异常消息时,将使用第一个和最后一个
    • 如果使用 JUNIT 3,则首选第 4 个
  • 有关详细信息,您可以阅读此文档JUnit5 用户指南了解详细信息。

评论

11赞 Nicolas Cornette 6/16/2016
对我来说,这是最好的答案,它非常清楚地涵盖了所有方式,谢谢!就我个人而言,即使使用 Junit4 以提高可读性,我也会继续使用第 3 个选项,为了避免空的 catch 块,您还可以捕获 Throwable 并断言 e
0赞 miuser 3/2/2017
是否可以使用 ExpectedException 来预期选中的异常?
0赞 Paul Samsotha 5/5/2018
它只是前三个答案的积累。IMO,如果它没有添加任何新内容,甚至不应该发布这个答案。只是为代表回答(一个受欢迎的问题)没用。
0赞 walsh 6/8/2018
当然,因为您可以将派生自的任何类型传递给方法。请看它的签名。@miuserTrowableExpectedException.expect
2赞 granadaCoder 1/20/2021
我要指出的是,Junit 4.13(强调“.13”)......支持 assertThrows。我赞成,因为这是一个很好的“完整概述”答案,但我希望你能考虑 4.13 的警告。
10赞 Mike Nakis 12/19/2015 #17

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 testing for "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 testMyFunction()
{
    RuntimeException e = expectException( RuntimeException.class, () -> 
        {
            myFunction();
        } );
    assert e.getMessage().equals( "I haz fail!" );
}

public void myFunction()
{
    throw new RuntimeException( "I haz fail!" );
}
42赞 weston 3/5/2016 #18

使用 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)

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

此处为 Maven/Gradle 说明。

评论

0赞 ycomp 3/22/2016
最简洁的方式,没有人欣赏它,奇怪。.assertJ 库只有一个问题,assertThat 在名称上与 junit 的冲突。更多关于 assertJ throwby: JUnit: Testing Exceptions with Java 8 and AssertJ 3.0.0 ~ Codeleak.pl
0赞 weston 3/24/2016
@ycomp 嗯,这是一个非常古老的问题的新答案,所以分数差异具有欺骗性。
0赞 Pierre Henry 3/25/2016
如果可以使用 Java 8 和 AssertJ,这可能是最好的解决方案!
0赞 mike rodent 10/12/2016
@ycomp我怀疑这种名称冲突可能是故意的:因此,AssertJ 库强烈建议您永远不要使用 JUnit ,始终使用 AssertJ 库。此外,JUnit 方法仅返回一个“常规”类型,而 AssertJ 方法返回一个子类......允许如上所述的方法串线(或任何技术术语......assertThatAbstractAssert
0赞 mike rodent 10/12/2016
@weston实际上,我刚刚在 AssertJ 2.0.0 中使用了您的技术。毫无疑问,没有理由不升级,但您可能想知道。
14赞 Daniel Käfer 7/24/2016 #19

JUnit 5 解决方案

import static org.junit.jupiter.api.Assertions.assertThrows;

@Test
void testFooThrowsIndexOutOfBoundsException() {    
  IndexOutOfBoundsException exception = expectThrows(IndexOutOfBoundsException.class, foo::doStuff);
     
  assertEquals("some message", exception.getMessage());
}

有关 JUnit 5 的更多信息 http://junit.org/junit5/docs/current/user-guide/#writing-tests-assertions

评论

0赞 Ilya Serbis 5/13/2020
expectThrows() 是 TestNG 的一部分,而不是 JUnit
1赞 Shirsh Sinha 10/29/2016 #20

举个例子,你想为下面提到的代码片段编写 Junit

public int divideByZeroDemo(int a,int b){

    return a/b;
}

public void exceptionWithMessage(String [] arr){

    throw new ArrayIndexOutOfBoundsException("Array is out of bound");
}

上面的代码是测试可能发生的一些未知异常,下面的代码是用自定义消息断言一些异常。

 @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"});
}
138赞 bric3 12/7/2016 #21

TL的;博士

  • post-JDK8 :使用 AssertJ 或自定义 lambda 来断言异常行为。

  • pre-JDK8 :我会推荐旧的好 - 块。(别忘了在 catch 块之前添加一个 fail() 断言trycatch)

无论是 Junit 4 还是 JUnit 5。

长话短说

可以自己编写一个自己动手 - 阻止或使用 JUnit 工具(或 JUnit 规则功能)。trycatch@Test(expected = ...)@Rule ExpectedException

但这些方法并不那么优雅,并且不能很好地与其他工具混合使用。此外,JUnit 工具确实存在一些缺陷。

  1. - 块,您必须围绕测试的行为编写块,并在 catch 块中写入断言,这可能很好,但许多人发现这种样式会中断测试的读取流程。此外,您需要在块的末尾写一个。否则,测试可能会遗漏断言的一侧;PMDfindbugsSonar 会发现此类问题。trycatchAssert.failtry

  2. 这个功能很有趣,因为你可以写更少的代码,然后编写这个测试应该不容易出现编码错误。这种方法在某些领域是缺乏的。@Test(expected = ...)

    • 如果测试需要检查异常的其他内容,例如原因或消息(好的异常消息非常重要,但拥有精确的异常类型可能还不够)。
    • 此外,由于期望被放置在方法中,具体取决于测试代码的编写方式,那么测试代码的错误部分可能会引发异常,导致误报测试,我不确定 PMDfindbugsSonar 是否会对此类代码给出提示。

      @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
      }
      
  3. 该规则也是修复先前警告的尝试,但是使用起来感觉有点尴尬,因为它使用了期望样式,EasyMock用户非常了解这种样式。这对某些人来说可能很方便,但如果你遵循行为驱动开发 (BDD) 或安排行为断言 (AAA) 原则,该规则将不适合这些写作风格。除此之外,它可能会遇到与方式相同的问题,具体取决于您期望的位置。ExpectedExceptionExpectedException@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
    }
    

    即使预期的异常放在测试语句之前,如果测试遵循 BDD 或 AAA,它也会中断您的读取流程。

    另外,请参阅作者在 JUnit 上的此评论问题。JUnit 4.13-beta-2 甚至弃用了这种机制:ExpectedException

    拉取请求 #1519:弃用 ExpectedException

    Assert.assertThrows 方法提供了一种更好的方法来验证异常。此外,当与其他规则(如 TestWatcher)一起使用时,使用 ExpectedException 很容易出错,因为在这种情况下,规则的顺序很重要。

因此,上述这些选项都有其所有警告,并且显然不能幸免于编码人员错误。

  1. 在创建这个看起来很有希望的答案后,我意识到了一个项目,它是陷阱例外

    正如项目描述所说,它允许编码人员用流畅的代码行来捕获异常,并为后一个断言提供此异常。您可以使用任何断言库,如 HamcrestAssertJ

    从主页上获取的快速示例:

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

    正如你所看到的,代码非常简单,你在特定行上捕获异常,API 是一个别名,它将使用 AssertJ API(类似于使用 )。在某种程度上,该项目依赖于 AssertJ 的祖先 FEST-Assert编辑:该项目似乎正在酝酿对 Java 8 Lambdas 的支持。thenassertThat(ex).hasNoCause()...

    目前,该库有两个缺点:

    • 在撰写本文时,值得注意的是,该库基于 Mockito 1.x,因为它在幕后创建了测试对象的模拟。由于 Mockito 仍未更新,因此该库无法与最终类或最终方法一起使用。即使它基于当前版本中的 Mockito 2,这也需要声明一个全局模拟制作者 (),这可能不是你想要的,因为这个模拟制作者与常规模拟制作者具有不同的缺点。inline-mock-maker

    • 它需要另一个测试依赖项。

    一旦库支持 lambda,这些问题将不适用。但是,AssertJ 工具集将复制该功能。

    考虑到所有因素,如果您不想使用 catch-exception 工具,我会推荐 try-catch 块的旧方法,至少到 JDK7。对于 JDK 8 用户,您可能更喜欢使用 AssertJ,因为它提供的可能不仅仅是断言异常。

  2. 随着 JDK8 的推出,lambda 进入了测试场景,它们已被证明是一种维护卓越行为的有趣方式。AssertJ 已更新,以提供一个很好的流畅 API 来断言异常行为。

    以及使用 AssertJ 的示例测试:

    @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");
    }
    
  3. 随着对 JUnit 5 的近乎完全的重写,断言得到了一些改进,它们可能被证明是一种开箱即用的正确断言异常的方法。但实际上断言 API 还是有点差,assertThrows 之外什么都没有。

    @Test
    @DisplayName("throws EmptyStackException when peeked")
    void throwsExceptionWhenPeeked() {
        Throwable t = assertThrows(EmptyStackException.class, () -> stack.peek());
    
        Assertions.assertEquals("...", t.getMessage());
    }
    

    正如你所注意到的,它仍然返回,因此不允许像 AssertJ 这样的链接断言。assertEqualsvoid

    此外,如果您记得名称与 或 的冲突,请准备好遇到与 相同的冲突。MatcherAssertAssertions

我想总结一下,今天(2017-03-03)AssertJ 的易用性、可发现的 API、快速的开发速度以及作为事实上的测试依赖是 JDK8 的最佳解决方案,无论测试框架如何(是否是 JUnit),以前的 JDK 应该依赖 try-catch 块,即使它们感觉笨拙。

这个答案是从另一个没有相同可见性的问题中复制的,我是同一个作者。

评论

1赞 anre 8/3/2017
添加 org.junit.jupiter:junit-jupiter-engine:5.0.0-RC2 依赖项(除了已经存在的 junit:junit:4.12)以便能够使用 assertThrows 可能不是首选解决方案,但不会给我带来任何问题。
0赞 BitfulByte 7/6/2018
我是使用 ExpectedException 规则的粉丝,但它总是困扰着我,因为它打破了 AAA。你写了一篇很棒的文章来描述所有不同的方法,你肯定鼓励我尝试 AssertJ :-)谢谢!
0赞 bric3 7/6/2018
@PimHazebroek谢谢。AssertJ API 非常丰富。在我看来,JUnit 提出的开箱即用的建议更好。
6赞 J-J 12/8/2016 #22

在 JUnit 4 或更高版本中,您可以按如下方式测试异常

@Rule
public ExpectedException exceptions = ExpectedException.none();


这提供了许多可用于改进 JUnit 测试的功能。
如果您看到下面的示例,我正在测试异常的 3 件事。

  1. 引发的异常类型
  2. 异常消息
  3. 异常的原因


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

}
54赞 Dilini Rajapaksha 1/10/2017 #23

更新:JUnit5 对异常测试进行了改进:。assertThrows

以下示例来自:Junit 5 用户指南

import static org.junit.jupiter.api.Assertions.assertThrows;

@Test
void exceptionTesting() {
    IllegalArgumentException 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!"));
    }
     
}

使用 Rule 进行测试。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 规则中阅读有关异常测试的更多信息。

1赞 fahrenx 10/1/2017 #24

在 Java 8 中,您可以创建一个方法,将要检查的代码和预期的异常作为参数:

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

然后在测试中:

expectException(() -> list.sublist(0, 2).get(2), IndexOutOfBoundsException.class);

好处:

  • 不依赖任何图书馆
  • 本地化检查 - 更精确,如果需要,允许在一次测试中进行多个这样的断言
  • 简单易用
91赞 NamshubWriter 10/2/2017 #25

现在 JUnit 5 和 JUnit 4.13 已经发布,最好的选择是使用 (对于 JUnit 5) 和 (对于 JUnit 4.13)。看 《JUnit 5 用户指南》。Assertions.assertThrows()Assert.assertThrows()

下面是一个示例,用于验证是否引发了异常,并使用 Truth 对异常消息进行断言:

public class FooTest {
  @Test
  public void doStuffThrowsIndexOutOfBoundsException() {
    Foo foo = new Foo();

    IndexOutOfBoundsException e = assertThrows(
        IndexOutOfBoundsException.class, foo::doStuff);

    assertThat(e).hasMessageThat().contains("woops!");
  }
}

与其他答案中的方法相比,其优点是:

  1. 内置于 JUnit 中
  2. 如果 lambda 中的代码未引发异常,您将收到一条有用的异常消息,如果引发其他异常,则会收到堆栈跟踪
  3. 简明
  4. 允许您的测试遵循 Arrange-Act-Assert
  5. 您可以精确地指出您期望抛出异常的代码
  6. 您不需要在子句中列出预期的例外throws
  7. 您可以使用所选的断言框架对捕获的异常进行断言

评论

0赞 Clockwork 3/8/2019
这种方法很干净,但我不明白这如何允许我们的测试遵循“Arrange-Act-Assert”,因为我们必须将“Act”部分包装在“assertThrow”中,这是一个断言。
0赞 NamshubWriter 3/12/2019
@Clockwork lambda 是“行为”。Arrange-Act-Assert 的目标是使代码干净、简单(因此易于理解和维护)。正如你所说,这种方法是干净的。
0赞 Clockwork 3/12/2019
我仍然希望我能在测试结束时,在“断言”部分断言投掷和异常。在此方法中,您需要将行为包装在第一个断言中,以便首先捕获它。
0赞 NamshubWriter 3/12/2019
这将需要在每次测试中都有更多的代码来执行断言。这是更多的代码,并且容易出错。
13赞 Dherik 1/25/2018 #26

我在 Mkyong 博客中找到的 Junit 4 最灵活、最优雅的答案。它具有使用注释的灵活性。我喜欢这种方法,因为您可以读取自定义异常的特定属性。try/catch@Rule

package com.mkyong;

import com.mkyong.examples.CustomerService;
import com.mkyong.examples.exception.NameNotFoundException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.hasProperty;

public class Exception3Test {

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    public void testNameNotFoundException() throws NameNotFoundException {

        //test specific type of exception
        thrown.expect(NameNotFoundException.class);

        //test message
        thrown.expectMessage(is("Name is empty!"));

        //test detail
        thrown.expect(hasProperty("errCode"));  //make sure getters n setters are defined.
        thrown.expect(hasProperty("errCode", is(666)));

        CustomerService cust = new CustomerService();
        cust.findByName("");

    }

}
4赞 Donatello 4/6/2018 #27

带有 Java8 的 Junit4 解决方案是使用这个函数:

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

然后用法是:

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

请注意,唯一的限制是在 lambda 表达式中使用对象引用。 此解决方案允许继续测试断言,而不是期望使用解决方案在方法级别进行 thowable。final@Test(expected = IndexOutOfBoundsException.class)

4赞 Piotr Rogowski 7/18/2018 #28

我建议库在junit测试中处理异常assertj-core

在 java 8 中,如下所示:

//given

//when
Throwable throwable = catchThrowable(() -> anyService.anyMethod(object));

//then
AnyException anyException = (AnyException) throwable;
assertThat(anyException.getMessage()).isEqualTo("........");
assertThat(exception.getCode()).isEqualTo(".......);
1赞 MangduYogii 4/16/2019 #29
    @Test(expectedException=IndexOutOfBoundsException.class) 
    public void  testFooThrowsIndexOutOfBoundsException() throws Exception {
         doThrow(IndexOutOfBoundsException.class).when(foo).doStuff();  
         try {
             foo.doStuff(); 
            } catch (IndexOutOfBoundsException e) {
                       assertEquals(IndexOutOfBoundsException .class, ex.getCause().getClass());
                      throw e;

               }

    }

这是检查方法是否抛出正确异常的另一种方法。

3赞 Ilya Serbis 5/13/2020 #30

JUnit 框架有 assertThrows() 方法:

ArithmeticException exception = assertThrows(ArithmeticException.class, () ->
    calculator.divide(1, 0));
assertEquals("/ by zero", exception.getMessage());