【问题标题】:How do you assert that a certain exception is thrown in JUnit 4 tests?您如何断言在 JUnit 4 测试中引发了某个异常?
【发布时间】:2010-09-14 11:09:33
【问题描述】:

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

虽然我当然可以这样做:

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

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

  assertTrue(thrown);
}

我记得有一个注解或一个 Assert.xyz 或 something 远不那么笨拙,而且在这类情况下更符合 JUnit 的精神。

【问题讨论】:

  • 任何其他方法的问题在于,一旦抛出异常,它们总是会结束测试。另一方面,我仍然经常希望使用各种参数调用org.mockito.Mockito.verify,以确保在引发异常之前发生某些事情(例如使用正确的参数调用记录器服务)。
  • 您可以在 JUnit wiki 页面 github.com/junit-team/junit/wiki/Exception-testing 中查看如何进行异常测试
  • @ZeroOne - 为此,我将进行两种不同的测试 - 一种用于异常,另一种用于验证与您的模拟的交互。
  • 有一种方法可以用 JUnit 5 做到这一点,我在下面更新了我的答案。
  • 这是一个很好的例子,在 JUnit4 和 JUnit5 中 how assert that an exception is Thrown

标签: java exception junit junit4 assert


【解决方案1】:

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

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

参考:

【讨论】:

  • 如果您希望仅在代码中的某个地方出现异常,而不是像这样的毯子,那么这段代码将不起作用。
  • @skaffman 这不适用于由 org.junit.experimental.theories.Theories 运行的 org.junit.experimental.theories.Theory
  • Roy Osherove 在单元测试的艺术 中不鼓励这种异常测试,因为异常可能在测试内部的任何地方,而不仅仅是在被测单元内部。跨度>
  • 我不同意@Kiview/Roy Osherove。在我看来,测试应该是针对行为的,而不是针对实现的。通过测试特定方法是否会引发错误,您可以将测试直接与实现联系起来。我认为上面显示的方法中的测试提供了更有价值的测试。我要补充的警告是,在这种情况下,我将测试自定义异常,以便我知道我得到了我真正想要的异常。
  • 两者都不是。我想测试班级的行为。重要的是,如果我尝试检索不存在的东西,我会遇到异常。响应get() 的数据结构是ArrayList 的事实是无关紧要的。如果我将来选择移动到原始数组,那么我将不得不更改此测试实现。应该隐藏数据结构,以便测试可以专注于的行为。
【解决方案2】:

编辑:现在 JUnit 5 和 JUnit 4.13 已经发布,最好的选择是使用 Assertions.assertThrows()(对于 JUnit 5)和 Assert.assertThrows()(对于 JUnit 4.13+)。详情请见my other answer

如果您尚未迁移到 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,测试将失败

详情请见this article

【讨论】:

  • @skaffman - 如果我理解正确的话,似乎 exception.expect 只在一个测试中应用,而不是整个班级。
  • 如果我们期望抛出的异常是已检查的异常,我们应该添加 throws 还是 try-catch 或以其他方式测试这种情况?
  • @MartinTrummer 在 foo.doStuff() 之后不应运行任何代码,因为抛出异常并退出该方法。在预期的异常(除了在 finally 中关闭资源)之后编写代码无论如何都是无益的,因为如果抛出异常,则永远不应该执行它。
  • 这是最好的方法。与 skaffman 的解决方案相比,这里有两个优点。首先,ExpectedException 类具有匹配异常消息的方法,甚至可以编写自己的依赖于异常类的匹配器。其次,你可以在你期望抛出异常的代码行之前设置你的期望——这意味着如果错误的代码行抛出异常,你的测试将失败;而 skaffman 的解决方案无法做到这一点。
  • @MJafarMash 如果您希望抛出的异常被选中,那么您可以将该异常添加到测试方法的 throws 子句中。每当您测试声明为抛出已检查异常的方法时,您都会执行相同的操作,即使在特定测试用例中未触发异常也是如此。
【解决方案3】:

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

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

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

应用判断。

【讨论】:

  • 也许我是老派,但我还是更喜欢这个。它还为我提供了一个测试异常本身的地方:有时我有某些值的 getter 异常,或者我可能只是在消息中查找特定值(例如在消息“无法识别的代码 'xyz' 中查找“xyz” ")。
  • 我认为 NamshubWriter 的方法可以让您两全其美。
  • 使用 ExpectedException 你可以调用 N exception.expect 每个方法来测试这样的 exception.expect(IndexOutOfBoundsException.class); foo.doStuff1(); exception.expect(IndexOutOfBoundsException.class); foo.doStuff2(); exception.expect(IndexOutOfBoundsException.class); foo.doStuff3();
  • @user1154664 实际上,你不能。使用 ExpectedException 只能测试一个方法抛出异常,因为当调用该方法时,测试将停止执行,因为它抛出了预期的异常!
  • 你的第一句话是不正确的。当使用ExpectedException 时,正常的做法是在您希望抛出异常的行之前设置期望值。这样,如果前面的行抛出异常,它不会触发规则,并且测试将失败。
【解决方案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

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

【讨论】:

【解决方案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,使用 Test 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();
        }
    }
    
  • 你也可以使用在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 5,那么你应该喜欢第一个
    • 当您只想测试异常类型时使用第二种方式
    • 第一个和最后两个用于进一步测试异常消息时使用
    • 如果您使用 junit 3,则首选第 4 个
  • 更多信息,您可以阅读this documentjunit5 user guide了解详情。

【讨论】:

  • 对我来说这是最好的答案,它非常清楚地涵盖了所有方式,谢谢!就我个人而言,即使使用 Junit4 以提高可读性,我也会继续使用第三个选项,为了避免空的 catch 块,您还可以捕获 Throwable 并断言 e 类型
  • 是否可以使用 ExpectedException 来期待已检查的异常?
  • 全部是前三个答案的积累。 IMO,如果没有添加任何新内容,甚至不应该发布此答案。只是为代表回答(一个流行的问题)。很没用。
  • 当然,因为您可以将派生自Trowable 的任何类型传递给方法ExpectedException.expect。请参阅it's signature。 @miuser
  • 这是最好的答案
【解决方案6】:

tl;dr

  • JDK8 后:使用 AssertJ 或自定义 lambdas 来断言异常行为。

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

无论是 Junit 4 还是 JUnit 5。

长篇大论

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

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

    1234563测试。此外,您需要在try 块的末尾写一个Assert.fail。否则,测试可能会错过断言的一侧; PMDfindbugsSonar 会发现此类问题。
  1. @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
      }
      
  2. 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 上的这个 comment 问题。 JUnit 4.13-beta-2 甚至不赞成这种机制:

    Pull request #1519: 弃用 ExpectedException

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

因此,上述这些选项有很多注意事项,并且显然无法避免编码错误。

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

    正如该项目的描述所说,它让编码人员编写流畅的代码行来捕获异常,并为后面的断言提供此异常。您可以使用任何断言库,例如 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();
    

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

    目前,这个库有两个缺点:

    • 在撰写本文时,值得注意的是,该库基于 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 的近乎完全重写,断言已经有点improved,它们可能被证明是一种开箱即用的正确断言异常的方法。但实际上断言 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) AssertJ 的易用性、可发现的 API、快速的开发速度以及 事实上的 em> 测试依赖是 JDK8 的最佳解决方案,无论测试框架如何(JUnit 与否),以前的 JDK 应该改为依赖 try-catch 块,即使他们觉得笨重。 p>

这个答案是从 another question 复制的,没有相同的可见性,我是同一作者。

【讨论】:

  • 添加 org.junit.jupiter:junit-jupiter-engine:5.0.0-RC2 依赖项(除了已经存在的 junit:junit:4.12 之外)能够使用 assertThrows 可能不是首选的解决方案,但没有给我带来任何问题。
  • 我很喜欢使用 ExpectedException 规则,但它总是让我感到困扰,因为它与 AAA 不同。你写了一篇很棒的文章来描述所有不同的方法,你肯定鼓励我尝试 AssertJ :-) 谢谢!
  • @PimHazebroek 谢谢。 AssertJ API 相当丰富。我认为 JUnit 开箱即用的建议更好。
【解决方案7】:

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

这是一个验证抛出异常的示例,并使用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. 您可以使用您选择的断言框架对捕获的异常进行断言

【讨论】:

  • 这种方法很干净,但我不明白这如何让我们的测试遵循“Arrange-Act-Assert”,因为我们必须将“Act”部分包装在“assertThrow”中,这是一个断言。
  • @Clockwork lambda 是“行为”。 Arrange-Act-Assert 的目标是使代码简洁明了(因此易于理解和维护)。正如你所说,这种方法很干净。
  • 我仍然希望我可以在测试结束时断言抛出和异常,在“断言”部分。在这种方法中,您需要将行为包装在第一个断言中以首先捕获它。
  • 这将需要在每个测试中使用更多代码来执行断言。那是更多的代码,而且很容易出错。
【解决方案8】:

如何:捕获一个非常普遍的异常,确保它脱离了 catch 块,然后断言该异常的类是您所期望的。如果 a) 异常的类型错误(例如,如果您有一个空指针)并且 b) 从未抛出异常,则此断言将失败。

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

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

  assertTrue(e instanceof IndexOutOfBoundsException);
}

【讨论】:

  • 另外,当测试失败的那一天,你不会在测试结果中看到什么样的 Exception ex。
  • 这可以通过改变你最后断言的方式来改进。 assertEquals(ExpectedException.class, e.getClass()) 会在测试失败时显示预期值和实际值。
【解决方案9】:

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

以下示例来自:Junit 5 User Guide

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

使用 JUnit 4 的原始答案。

有几种方法可以测试是否引发了异常。我还在我的帖子How to write great unit tests with JUnit

中讨论了以下选项

设置expected参数@Test(expected = FileNotFoundException.class)

@Test(expected = FileNotFoundException.class) 
public void testReadFile() { 
    myClass.readFile("test.txt");
}

使用trycatch

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 for Exception testingbad.robot - Expecting Exceptions JUnit Rule 中阅读有关异常测试的更多信息。

【讨论】:

    【解决方案10】:

    使用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 instructions here.

    【讨论】:

    • 最简洁的方式,没有人欣赏它,奇怪.. 我对 assertJ 库只有一个问题,assertThat 与 junit 的名称冲突。更多关于 assertJ throwby 的信息:JUnit: Testing Exceptions with Java 8 and AssertJ 3.0.0 ~ Codeleak.pl
    • @ycomp 嗯,这是一个非常老问题的新答案,所以分数差异具有欺骗性。
    • 如果可以使用 Java 8 和 AssertJ,这可能是最好的解决方案!
    • @ycomp 我怀疑这种名称冲突可能是设计使然:AssertJ 库因此强烈建议您不要使用 JUnit assertThat,始终使用 AssertJ 库。 JUnit 方法也只返回一个“常规”类型,而 AssertJ 方法返回一个AbstractAssert 子类......允许如上所述的方法字符串(或任何技术术语......)。
    • @weston 实际上我刚刚在 AssertJ 2.0.0 中使用了您的技术。毫无疑问,没有理由不升级,但您可能想知道。
    【解决方案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
    

    【讨论】:

      【解决方案12】:

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

      使用这个小助手你会写

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

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

      【讨论】:

      • 我也想过做这样的事情,但最终发现ExpectedException的真正强大之处在于不仅可以指定预期的异常,还可以指定异常的某些属性,例如预期的原因或预期的消息。
      • 我猜这个解决方案有一些与模拟相同的缺点?例如,如果 foofinal 它将失败,因为您无法代理 foo?
      • Tom,如果 doStuff() 是接口的一部分,则代理方法将起作用。否则这种方法会失败,你是对的。
      【解决方案13】:

      您也可以这样做:

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

      【讨论】:

      • 在 JUnit 测试中,最好使用Assert.fail(),而不是assert,以防您的测试在未启用断言的环境中运行。
      【解决方案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】:

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

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

        【讨论】:

          【解决方案16】:

          JUnit 5 解决方案

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

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

          【讨论】:

          【解决方案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(Arrange Act Assert),您希望在实际不同的步骤中执行 Act 和 Assert 步骤。
          • @bln-tom 从技术上讲,这是两个不同的步骤,只是它们的顺序不同。 ;p
          【解决方案18】:

          JUnit 对此具有内置支持,带有 "expected" attribute

          【讨论】:

            【解决方案19】:

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

            (taken from my blog)

            如下使用:

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

            【讨论】:

              【解决方案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("no exception thrown");
              【解决方案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 项测试。

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

                【讨论】:

                  【解决方案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());
                  }
                  

                  【讨论】:

                  • 第二个catch 会在抛出其他异常时吞下堆栈跟踪,从而丢失有用信息
                  【解决方案24】:

                  除了NamShubWriter 所说的,请确保:

                  • ExpectedException 实例是public (Related Question)
                  • ExpectedException 没有在 @Before 方法中实例化。这个post 清楚地解释了 JUnit 执行顺序的所有复杂性。

                  不要不要这样做:

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

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

                  【讨论】:

                    【解决方案25】:

                    Junit4 用 Ja​​va8 的解决方案就是使用这个函数:

                    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 框架有 assertThrows() 方法:

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

                        【讨论】:

                          【解决方案28】:

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

                          【讨论】:

                            【解决方案29】:

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

                            好处:

                            • 不依赖任何库
                            • 本地化检查 - 更精确,如果需要,允许在一个测试中拥有多个这样的断言
                            • 易于使用

                            【讨论】:

                              【解决方案30】:
                                  @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;
                              
                                             }
                              
                                  }
                              

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

                              【讨论】:

                                猜你喜欢
                                • 2019-01-28
                                • 2020-04-20
                                • 1970-01-01
                                • 2014-06-13
                                • 2012-01-24
                                • 1970-01-01
                                • 2018-04-19
                                • 2012-08-28
                                相关资源
                                最近更新 更多