我该如何惯用JUnit4来测试某些代码引发异常?

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

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

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

  assertTrue(thrown);
}


我回想一下,在这种情况下,有一个批注或一个Assert.xyz或一些不太灵活的JUnit东西。

评论

任何其他方法的问题在于,一旦抛出异常,它们总是会终止测试。另一方面,我通常仍想使用各种参数调用org.mockito.Mockito.verify,以确保在引发异常之前发生了某些事情(例如,使用正确的参数调用了logger服务)。 />
您可以在JUnit Wiki页面github.com/junit-team/junit/wiki/Exception-testing
上看到如何进行异常测试。
@ZeroOne-为此,我将进行两种不同的测试-一种用于测试异常,另一种用于验证与您的模拟的交互。

使用JUnit 5可以做到这一点,我在下面更新了我的答案。

#1 楼

这取决于JUnit版本和使用的断言库。对于JUnit5和4.13,请参见答案https://stackoverflow.com/a/2935935/2986984

如果您使用assertJ或google-truth,请参阅答案https://stackoverflow.com/a/41019785/2986984


JUnit <= 4.12的原始答案是:

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

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

}


尽管回答https://stackoverflow.com/a/31826781/2986984为JUnit <= 4.12提供了更多选项。

参考:


JUnit测试常见问题解答


评论


如果您只希望在代码中的某个地方(而不是像这样的毯子)遇到异常,那么这段代码将无法工作。

–哦,Chin Boon
2011年6月27日14:50

@skaffman这不适用于由org.junit.experimental.theories.Theories运行的org.junit.experimental.theories.Theory

– Artem Oboturov
2012年4月27日在16:01



Roy Osherove不鼓励在单元测试的领域中进行此类异常测试,因为异常可能在测试内部的任何地方,而不仅是在被测单元内部。

–凯文·威特克(Kevin Wittek)
2015年1月22日14:36



我不同意@ Kiview / Roy Osherove。我认为,测试应该针对行为,而不是针对实施。通过测试特定方法会引发错误,您可以将测试直接与实现联系在一起。我认为上面显示的方法中的测试提供了更有价值的测试。我要补充的一点是,在这种情况下,我将测试自定义异常,以使我知道自己正在获取我真正想要的异常。

–nickbdyer
16年3月3日,12:22



都不行我想测试该类的行为。重要的是,如果我尝试检索不存在的内容,则会出现异常。数据结构是响应get()的ArrayList的事实是无关紧要的。如果将来选择迁移到原始数组,则必须更改此测试实现。数据结构应该被隐藏,以便测试可以将重点放在类的行为上。

–nickbdyer
16年5月3日在16:33

#2 楼

编辑:现在已经发布了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)好得多,因为如果在IndexOutOfBoundsException之前抛出foo.doStuff(),则测试将失败。
有关详细信息,请参见本文。

评论


@skaffman-如果我正确地理解了这一点,它看起来就像是exception.expect仅在一个测试中应用,而不是在整个类中应用。

– bacar
2012年7月6日上午11:41

如果我们希望引发的异常是一个已检查的异常,是否应该添加throw或try-catch或以其他方式测试这种情况?

–穆罕默德·贾法尔·马什哈迪(Mohammad Jafar Mashhadi)
13年6月29日在8:05

@MartinTrummer在foo.doStuff()之后不应运行任何代码,因为会引发异常并退出该方法。无论如何,将代码放在预期的异常之后(最后关闭资源除外)无济于事,因为如果引发异常,则永远不应执行该代码。

–詹森·汤普森(Jason Thompson)
2014年1月17日15:59



这是最好的方法。与skaffman的解决方案相比,这里有两个优点。首先,ExpectedException类具有匹配异常消息的方法,甚至可以编写自己的依赖于异常类的匹配器。其次,您可以在期望引发异常的代码行之前立即设置期望值-这意味着,如果错误的代码行引发异常,则测试将失败。而用skaffman的解决方案则无法做到这一点。

–达伍德·伊本·卡里姆(Dawood ibn Kareem)
2014年7月26日上午10:58

@MJafarMash如果选中了您希望引发的异常,则可以将该异常添加到测试方法的throws子句中。在测试声明为引发已检查异常的方法时,即使在特定测试用例中未触发该异常,也可以执行相同的操作。

– NamshubWriter
2015年5月10日14:38

#3 楼

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

我倾向于将其用于测试参数验证,因为此类方法通常是非常简单但更复杂的测试可能会更好地配合使用:

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


应用判断。

评论


也许我是老学校,但我还是喜欢这个。它还为我提供了一个测试异常本身的地方:有时我在某些值的getters中存在异常,或者我可能只是在消息中寻找特定值(例如,在消息“无法识别的代码'xyz'中寻找“ xyz” ”)。

–罗德尼·吉茨(Rodney Gitzel)
2010-10-6 17:22

我认为NamshubWriter的方法可以为您提供两全其美的体验。

–艾迪
2011-3-9在19:21

使用ExpectedException,您可以调用N exception.expect每个方法来进行测试,例如:exception.expect(IndexOutOfBoundsException.class); foo.doStuff1(); exception.expect(IndexOutOfBoundsException.class); foo.doStuff2(); exception.expect(IndexOutOfBoundsException.class); foo.doStuff3();

–user1154664
2012年10月9日17:07



@ user1154664实际上,您不能。使用ExpectedException,您只能测试一个方法引发异常,因为调用该方法时,测试将因为引发了预期的异常而停止执行!

– NamshubWriter
2014年2月24日在16:26

您的第一句话就是错误的。使用ExpectedException时,通常要做的是在期望引发异常的行之前立即设置期望值。这样,如果更早的一行抛出异常,它将不会触发该规则,并且测试将失败。

–达伍德·伊本·卡里姆(Dawood ibn Kareem)
14年7月26日在10:53

#4 楼

如前所述,在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

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

评论


我喜欢这个解决方案,但是可以从Maven仓库下载吗?

–塞尔温
2015年3月31日在2:07

@Airduster在Maven上可用的这一想法的一种实现是stefanbirkner.github.io/vallado

– NamshubWriter
2015年5月10日14:35

@CristianoFontes将为JUnit 4.13定义此API的更简单版本。见github.com/junit-team/junit/commit/…

– NamshubWriter
15年7月21日在2:18

@RafalBorowiec从技术上讲,新的DummyService():: someMethod是MethodHandle,但是这种方法对lambda表达式同样有效。

–安迪
17年4月28日在19:48

@NamshubWriter,似乎junit 4.13被抛弃,转而支持junit 5:stackoverflow.com/questions/156503/…

–Vadzim
17/12/19在11:02



#5 楼

在junit中,有四种测试异常的方法。

junit5.x



对于junit5.x,可以按以下方式使用assertThrows

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



junit4.x



对于junit4.x,请使用测试注解的可选'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();
    }
}



也可以使用经典在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.
    }
}



so


如果您喜欢junit 5,那么您应该喜欢第一种方法
当您只想测试异常类型时使用第二种方法
当您想进一步测试异常消息时使用前两种方法
有关详细信息,请阅读junit 3,然后选择第4个。


有关更多信息,请阅读此文档和junit5用户指南以获取详细信息。


评论


对我来说,这是最好的答案,它非常清楚地涵盖了所有方面,谢谢!我个人甚至在使用Junit4的情况下仍继续使用3rd选项,以提高可读性,为避免出现空的catch块,您还可以捕获Throwable和assert类型的e

– Nicolas Cornette
16 Jun 16'在8:57



是否可以使用ExpectedException期望检查异常?

–miuser
17年3月2日在9:23

所有这些都是前三个答案的累加。 IMO,如果它没有添加任何新内容,那么甚至不应该发布此答案。只需回答代表的一个(受欢迎的问题)即可。真没用。

–Paul Samsotha
18年5月5日在1:27



当然,因为您可以将任何从Trowable派生的类型传递给ExpectedException.expect方法。请查看其签名。 @miuser

–沃尔什
18 Jun 8'在13:47



这是最好的答案

– Aneesh Vijendran
7月3日18:45

#6 楼

tl; dr


后JDK8:使用AssertJ或自定义lambda来声明异常行为。
pre-JDK8:我将推荐旧的良好的try-catch块。 (不要忘记在fail()块之前添加一个catch断言)

不管是Junit 4还是JUnit5。

长话短说

可以自己编写一个try-catch块,也可以使用JUnit工具(@Test(expected = ...)@Rule ExpectedException JUnit规则功能)。

但是这些方法并不那么优雅并且不能很好地混合明智地使用其他工具。而且,JUnit工具确实存在一些陷阱。



try-catch块必须围绕测试的行为编写该块并将断言写入catch块,这也许很好,但许多人发现这种风格打断了测试的阅读流程。另外,您需要在Assert.fail块的末尾编写一个try。否则,测试可能会遗漏断言的某一方面; PMD,findbugs或Sonar会发现此类问题。

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


如果测试需要检查异常的其他内容,例如原因或消息(好的异常消息非常重要,则有一个精确的异常类型可能还不够)。

方法中也期望值很高,这取决于测试代码的编写方式,然后测试代码的错误部分会引发异常,导致测试结果为假,我不确定PMD,findbug或Sonar是否会提示此类代码。

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





ExpectedException规则也是试图解决以前的警告,但由于使用了期望样式,因此使用起来有点尴尬,EasyMock用户非常了解这种样式。对于某些人来说可能很方便,但是如果您遵循行为驱动开发(BDD)或“安排行为声明”(AAA)原则,则ExpectedException规则将不适合那些写作风格。除此之外,它可能会遇到与@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,则会中断您的阅读流程。

此外,请参阅ExpectedException作者JUnit上的此注释问题。 JUnit 4.13-beta-2甚至不赞成使用此机制:


拉取请求#1519:不赞成ExpectedException

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



因此上述这些选项具有他们所有的警告,并显然无法避免编码错误。



我有一个项目在创建了这个看起来很有希望的答案之后才意识到,这是一个例外。

正如该项目的描述所言,它使程序员可以用流畅的代码编写捕获异常的代码,并为后者的断言提供此异常。您可以使用任何断言库,例如Hamcrest或AssertJ。

从主页获取的快速示例:

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


您可以看到该代码非常简单,您可以在特定的行上捕获异常,then API是将使用AssertJ API的别名(类似于使用assertThat(ex).hasNoCause()...)。在某些时候,该项目依赖于FEST-声明AssertJ的祖先。编辑:看来该项目正在酝酿对Java 8 Lambdas的支持。

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

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

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


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



通过对JUnit 5的近乎完全的重写,断言已得到了一些改进,作为断言正确地声明异常的开箱即用的方式,它们可能很有趣。但是实际上断言API仍然有点差,assertThrows之外没有任何东西。

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

    Assertions.assertEquals("...", t.getMessage());
}


您注意到assertEquals仍在返回void,因此不允许像AssertJ这样的链式断言。

如果您还记得的话名称与MatcherAssert发生冲突,请准备好与Assertions发生相同的冲突。


我想得出一个结论,今天(2017-03-03)无论使用哪种测试框架(无论是否使用JUnit,AssertJ的易用性,可发现的API,快速的开发速度以及事实上的测试依赖性)都是使用JDK8的最佳解决方案。 ),即使以前的JDK感到笨拙,它们也应该依赖try-catch块。

这个答案是从另一个问题获得的,该问题的知名度不一样,我是同一位作者。

评论


添加org.junit.jupiter:junit-jupiter-engine:5.0.0-RC2依赖关系(除了已经存在的junit:junit:4.12之外),以便能够使用assertThrows可能不是首选的解决方案,但是并没有引起任何后果。给我的问题。

– anre
17年8月3日在12:13

我喜欢使用ExpectedException规则,但是它总是使我不安,因为它会破坏AAA。您已经写了一篇很棒的文章来描述所有不同的方法,并且绝对鼓励我尝试AssertJ :-)谢谢!

– Pim Hazebroek
18年7月6日在6:34

@PimHazebroek谢谢。 AssertJ API非常丰富。我认为JUnit提出的建议更好。

– Brice
18年7月6日在7:54

#7 楼

现在已经发布了JUnit 5和JUnit 4.13,最好的选择是使用Assertions.assertThrows()(对于JUnit 5)和Assert.assertThrows()(对于JUnit 4.13)。请参见
Junit 5用户指南。

下面是一个示例,该示例验证抛出的异常,并使用Truth对异常消息进行断言:

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

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

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


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


内置到JUnit中
如果lambda中的代码得到了有用的异常消息不会引发异常,如果引发其他异常,则不会引发堆栈跟踪
简洁
让您的测试遵循Arrange-Act-Assert
您可以精确地指出您期望抛出的代码异常
您不需要在throws子句中列出预期的异常
您可以使用您选择的断言框架对捕获的异常进行断言

方法将添加到JUnit 4.13中的org.junit Assert

评论


这种方法是干净的,但是我不知道这如何使我们的测试遵循“ Arrange-Act-Assert”,因为我们必须将“ Act”部分包装在“ assertThrow”(即一个断言)中。

–钟表
19 Mar 8 '19在15:19

@Clockwork lambda是“行为”。 Arrange-Act-Assert的目标是使代码简洁明了(从而易于理解和维护)。如您所说,这种方法是干净的。

– NamshubWriter
19年3月11日17:10



我仍然希望我可以在“断言”部分中在测试结束时声明抛出和异常。在这种方法中,您需要将动作包装在第一个断言中以首先捕获它。

–钟表
19年3月11日在18:02

这将需要在每个测试中使用更多代码来进行断言。那是更多的代码,并且容易出错。

– NamshubWriter
19年3月11日在18:36

#8 楼

怎么样:捕获一个非常普通的异常,确保它使它脱离catch块,然后断言该异常的类就是您期望的异常。如果a)异常的类型错误(例如,如果您改为使用Null指针),并且b)从未引发异常,则此断言将失败。
public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

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

  assertTrue(e instanceof IndexOutOfBoundsException);
}


评论


同样,当测试失败的一天到来时,您将看不到测试结果中的异常类型。

– jontejj
13年3月14日在16:13

通过更改最后的声明方式,可以对此进行一些改进。当测试失败时,assertEquals(ExpectedException.class,e.getClass())将向您显示期望值和实际值。

–密码
18-11-15在0:54

#9 楼

使用可以与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指令。

评论


最简洁的方法,没人能理解,奇怪。我对assertJ库只有一个问题,assertThat与junit的名称冲突。有关assertJ throwby的更多信息:JUnit:使用Java 8和AssertJ 3.0.0测试异常〜Codeleak.pl

– ycomp
16年3月21日在20:03

@ycomp好吧,这是一个很老的问题的新答案,因此得分差异具有欺骗性。

–weston
16 Mar 24 '16 at 8:46

如果可以使用Java 8和AssertJ,那可能是最好的解决方案!

–皮埃尔·亨利(Pierre Henry)
16 Mar 25 '16 at 15:01

@ycomp我怀疑此名称冲突可能是设计使然:AssertJ库因此强烈鼓励您不要使用JUnit assertThat,始终使用AssertJ。同样,JUnit方法仅返回“常规”类型,而AssertJ方法则返回AbstractAssert子类...允许如上所述的方法进行字符串化(或为此使用任何技术术语...)。

–麦克·啮齿动物
16-10-11在16:13

实际上,@ weston我只是在AssertJ 2.0.0中使用了您的技术。毫无疑问,没有升级的借口,尽管您可能想知道。

–麦克·啮齿动物
16-10-11在16:14

#10 楼


BDD样式解决方案:JUnit 4 +捕获异常+ 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


#11 楼

更新: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的更多信息。robot-预期异常JUnit规则。

#12 楼

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

使用这个小帮手,您将编写

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


这比JUnit 4.7的ExpectedException规则要详细。
与skaffman提供的解决方案相比,您可以指定期望的代码行例外。希望对您有所帮助。

评论


我也考虑过做类似的事情,但最终发现ExpectedException的真正功能是不仅可以指定预期的异常,而且还可以指定异常的某些属性,例如预期的原因或预期的消息。

–詹森·汤普森(Jason Thompson)
2014年1月17日下午16:28

我的猜测是该解决方案具有与模拟相同的缺点吗?例如,如果foo是final,则它将失败,因为您无法代理foo?

–汤姆
2014年6月20日19:39

汤姆,如果doStuff()是接口的一部分,则代理方法将起作用。否则,这种方法将失败,您是对的。

–rwitzel
15年1月16日在9:49

#13 楼

您也可以执行以下操作:

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


评论


在JUnit测试中,最好使用Assert.fail()而不是断言,以防万一您的测试在未启用断言的环境中运行。

– NamshubWriter
2015年1月14日下午4:51

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


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

#15 楼

JUnit 5解决方案

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

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


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

评论


ExpectThrows()是TestNG的一部分,而不是JUnit

–Lu55
5月12日21:35

#16 楼

我在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("");

    }

}


#17 楼

我在这里尝试了许多方法,但是它们要么很复杂,要么根本无法满足我的要求。实际上,可以非常简单地编写辅助方法:

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;并且在期末课程上也能正常工作。

评论


有趣,但不适合AAA(安排法案断言),您要在实际上不同的步骤中执行法案和断言步骤。

–嘎嘎叫
2014年10月28日在21:15

@ bln-tom从技术上讲这是两个不同的步骤,只是顺序不一样。 ; p

–特雷卡兹
2015年2月2日,下午3:52

#18 楼

Java 8解决方案

如果您希望使用以下解决方案:


使用Java 8 lambdas
不依赖于任何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!" );
}


#19 楼

JUnit对此具有内置支持,并带有“ expected”属性。

#20 楼

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


评论


在带有} catch()的行之前,您应该插入fail(“不抛出异常”);

–丹尼尔·阿尔德(Daniel Alder)
17年12月18日在9:40

#21 楼

只需制作一个可以关闭和打开的Matcher,就像这样:

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


#22 楼

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

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


这提供了许多功能,可用于改进我们的JUnit测试。如果您看到以下示例,则我正在测试有关异常的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");
    }

}


#23 楼

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

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


评论


如果抛出其他异常,第二个捕获将吞没堆栈跟踪,从而丢失有用的信息

– NamshubWriter
15年8月30日在1:06

#24 楼

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


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

不要这样做:

@Rule    
public ExpectedException expectedException;

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


最后,该博客文章清楚地说明了如何断言某个异常被抛出。

#25 楼

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表达式中使用final对象引用。
此解决方案允许继续测试断言,而不是期望使用@Test(expected = IndexOutOfBoundsException.class)解决方案在方法级别可访问。

#26 楼

我建议使用库assertj-core处理junit测试中的异常

在Java 8中,如下所示:

//given

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

//then
AnyException anyException = (AnyException) throwable;
assertThat(anyException.getMessage()).isEqualTo("........");
assertThat(exception.getCode()).isEqualTo(".......);


#27 楼

例如,您要为下面提到的代码片段编写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"});
}


#28 楼

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

               }

    }


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

#29 楼

JUnit框架具有assertThrows()方法:

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



对于JUnit 5,它在org.junit.jupiter.api.Assertions类中;对于
,对于JUnit 4.13,它在org.junit.Assert类中;
对于早期版本的JUnit 4:只需将org.junit.jupiter:junit-jupiter-api上的引用添加到您的项目中,您将从JUnit 5中获得运行良好的版本。


#30 楼

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


优点:


不依赖任何库
本地化检查-更精确,并且如果需要,可以在一个测试中包含多个这样的断言
易于使用