如何以惯用方式使用 JUnit 来测试某些代码是否引发异常?
虽然我当然可以做这样的事情:
@Test
public void testFooThrowsIndexOutOfBoundsException() {
boolean thrown = false;
try {
foo.doStuff();
} catch (IndexOutOfBoundsException e) {
thrown = true;
}
assertTrue(thrown);
}
我记得有一个注释或一个 Assert.xyz 或其他东西,在这种情况下,JUnit 的精神远不那么笨拙,而且更符合 JUnit 的精神。
org.mockito.Mockito.verify
以确保在引发异常之前发生某些事情(例如使用正确的参数调用记录器服务)。
这取决于 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 测试-FAQ
编辑: 现在 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。
ExpectedException
类具有匹配异常消息的方法,甚至可以根据异常类编写自己的匹配器。其次,你可以在你期望抛出异常的代码行之前设置你的期望——这意味着如果错误的代码行抛出异常,你的测试将会失败;而 skaffman 的解决方案无法做到这一点。
小心使用预期异常,因为它只断言该方法抛出了该异常,而不是测试中的特定代码行。
我倾向于使用它来测试参数验证,因为这些方法通常非常简单,但更复杂的测试可能会更好地使用:
try {
methodThatShouldThrow();
fail( "My method didn't throw when I expected it to" );
} catch (MyException expectedException) {
}
应用判断。
ExpectedException
时,通常的做法是在您希望引发异常的行之前设置期望值。这样,如果前面的行抛出异常,它不会触发规则,并且测试将失败。
如前所述,在 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
披露:我是博客和项目的作者。
new DummyService()::someMethod
是 MethodHandle
,但这种方法同样适用于 lambda 表达式。
在junit中,有四种方法可以测试异常。
junit5.x
对于junit5.x,您可以使用assertThrows,如下所示@Test public void testFooThrowsIndexOutOfBoundsException() { Throwable exception = assertThrows(IndexOutOfBoundsException.class, () -> foo.doStuff()); assertEquals("预期消息", exception.getMessage()); }
junit4.x
对于 junit4.x,使用 Test annonation @Test(expected = IndexOutOfBoundsException.class) public void testFooThrowsIndexOutOfBoundsException() { foo.doStuff(); }
对于 junit4.x,使用 ExpectedException 规则 public class XxxTest { @Rule public ExpectedException throw = ExpectedException.none(); @Test public void testFooThrowsIndexOutOfBoundsException() { throw.expect(IndexOutOfBoundsException.class) //可以像 throwed.expectMessage("expected messages"); 一样测试异常消息foo.doStuff(); } }
您还可以使用在 junit 3 框架下广泛使用的经典 try/catch 方式 @Test public void testFooThrowsIndexOutOfBoundsException() { try { foo.doStuff(); fail("预期的异常没有发生。"); } catch(IndexOutOfBoundsException e) { //如果执行到这里, //表示发生了这个异常。 //所以我们不需要处理它。 } }
因此,如果您喜欢 junit 5,那么您应该喜欢第一种方式,当您只想测试异常类型时使用第二种方式,如果您使用 junit 3,则在您想要进一步测试异常消息时使用第一种和最后两种方式,然后第4个是首选
如果你喜欢junit 5,那么你应该喜欢第一个
当您只想测试异常类型时使用第二种方法
当您想要进一步测试异常消息时使用第一个和最后两个
如果您使用 junit 3,则首选第 4 个
有关详细信息,您可以阅读此文档和 junit5 用户指南了解详细信息。
tl;博士
post-JDK8 :使用 AssertJ 或自定义 lambda 断言异常行为。
pre-JDK8:我会推荐旧的好 try-catch 块。 (不要忘记在 catch 块之前添加一个 fail() 断言)
不管是 Junit 4 还是 JUnit 5。
长篇大论
可以为自己编写一个自己动手 try
-catch
块或使用 JUnit 工具(@Test(expected = ...)
或 @Rule ExpectedException
JUnit 规则功能)。
但是这些方法并不那么优雅,并且不能很好地将可读性与其他工具混合。此外,JUnit 工具确实存在一些缺陷。
try-catch 块您必须围绕测试的行为编写块并在 catch 块中写入断言,这可能很好,但许多人发现这种样式会中断测试的读取流程。此外,您需要在 try 块的末尾编写 Assert.fail。否则,测试可能会错过断言的一侧; PMD、findbugs 或 Sonar 会发现此类问题。 @Test(expected = ...) 功能很有趣,因为您可以编写更少的代码,然后编写此测试据说不太容易出现编码错误。但这种方法在某些领域是缺乏的。如果测试需要检查异常的其他内容,例如原因或消息(好的异常消息非常重要,拥有精确的异常类型可能还不够)。此外,由于期望被放置在方法中,根据测试代码的编写方式,测试代码的错误部分可能会引发异常,导致误报测试,我不确定 PMD、findbugs 或 Sonar将提供有关此类代码的提示。 @Test(expected = WantedException.class) public void call2_should_throw_a_WantedException__not_call1() { // 初始化测试tested.call1(); // 可能会抛出 WantedException // 调用进行实际测试 test.call2(); // 应该引发异常的调用 } ExpectedException 规则也是尝试修复之前的警告,但是使用起来感觉有点别扭,因为它使用了期望样式,EasyMock 用户非常了解这种样式。这对某些人来说可能很方便,但是如果您遵循行为驱动开发 (BDD) 或安排行为断言 (AAA) 原则,ExpectedException 规则将不适合这些写作风格。除此之外,它可能会遇到与@Test 方式相同的问题,具体取决于您将期望放在哪里。 @Rule ExpectedException throw = ExpectedException.none() @Test public void call2_should_throw_a_WantedException__not_call1() { // 期望 throwed.expect(WantedException.class); throw.expectMessage("boom"); // 初始化测试tested.call1(); // 可能会抛出 WantedException // 调用进行实际测试 test.call2(); // 应该引发异常的调用 } 即使预期的异常放在测试语句之前,如果测试遵循 BDD 或 AAA,它也会破坏您的阅读流程。另外,请参阅 ExpectedException 作者的 JUnit 上的此评论问题。 JUnit 4.13-beta-2 甚至弃用了这种机制: Pull request #1519: Deprecate ExpectedException Assert.assertThrows 方法为验证异常提供了更好的方法。此外,ExpectedException 的使用在与 TestWatcher 等其他规则一起使用时容易出错,因为在这种情况下规则的顺序很重要。
因此,上述这些选项有很多警告,显然不能避免编码错误。
在创建这个看起来很有希望的答案后,我意识到了一个项目,它是捕获异常。正如该项目的描述所说,它让编码人员用流畅的代码行编写捕获异常并为后一个断言提供此异常。您可以使用任何断言库,例如 Hamcrest 或 AssertJ。取自主页的快速示例: // 给出:一个空列表 List myList = new ArrayList(); // when: 我们尝试获取列表的第一个元素 when(myList).get(1); // then: 我们期望 IndexOutOfBoundsException then(caughtException()) .isInstanceOf(IndexOutOfBoundsException.class) .hasMessage("Index: 1, Size: 0") .hasNoCause();如您所见,代码非常简单,您在特定行捕获异常,然后 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,因为它提供的可能不仅仅是断言异常。在 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() { ... // 当 Throwable throw = catchThrowable(() -> someBadIOOperation()); // 然后 assertThat(throw).isInstanceOf(Exception.class) .hasMessageContaining("boom"); } 随着对 JUnit 5 的近乎完全的重写,断言得到了一些改进,它们可能被证明是一种有趣的开箱即用的方式来正确断言异常。但实际上断言 API 还是有点差,除了 assertThrows 之外什么都没有。 @Test @DisplayName("查看时抛出 EmptyStackException") void throwsExceptionWhenPeeked() { Throwable t = assertThrows(EmptyStackException.class, () -> stack.peek()); Assertions.assertEquals("...", t.getMessage());正如您所注意到的,assertEquals 仍然返回 void,因此不允许像 AssertJ 那样链接断言。此外,如果您记得与 Matcher 或 Assert 的名称冲突,请准备好与 Assertions 遇到相同的冲突。
我想总结一下今天 (2017-03-03) AssertJ 的易用性、可发现的 API、快速的开发速度以及作为事实上的测试依赖是 JDK8 的最佳解决方案,无论测试框架(JUnit 与否)如何,以前的 JDK 都应该依赖 try
-catch
块,即使它们感觉很笨重。
这个答案是从 another question 复制的,没有相同的可见性,我是同一作者。
现在 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!");
}
}
与其他答案中的方法相比的优点是:
内置于 JUnit 如果 lambda 中的代码没有抛出异常,您会收到有用的异常消息,如果抛出不同的异常,则会收到堆栈跟踪 简洁 允许您的测试遵循 Arrange-Act-Assert 您可以准确地指出您是什么代码期望抛出异常您不需要在 throws 子句中列出预期的异常您可以使用您选择的断言框架来对捕获的异常进行断言
怎么样:捕获一个非常普遍的异常,确保它脱离了 catch 块,然后断言异常的类是你所期望的。如果 a) 异常的类型错误(例如,如果您有一个空指针)并且 b) 从未抛出异常,则此断言将失败。
public void testFooThrowsIndexOutOfBoundsException() {
Throwable e = null;
try {
foo.doStuff();
} catch (Throwable ex) {
e = ex;
}
assertTrue(e instanceof IndexOutOfBoundsException);
}
assertEquals(ExpectedException.class, e.getClass())
将在测试失败时向您显示预期值和实际值。
更新: 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");
}
使用 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 for Exception testing 和 bad.robot - Expecting Exceptions JUnit Rule 中阅读有关异常测试的更多信息。
使用可与 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 instructions here.
assertThat
,始终使用 AssertJ。 JUnit 方法也只返回一个“常规”类型,而 AssertJ 方法返回一个 AbstractAssert
子类......允许如上所述的方法字符串(或任何技术术语......)。
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
为了解决同样的问题,我建立了一个小项目:http://code.google.com/p/catch-exception/
使用这个小助手你会写
verifyException(foo, IndexOutOfBoundsException.class).doStuff();
这比 JUnit 4.7 的 ExpectedException 规则更简洁。与 skaffman 提供的解决方案相比,您可以指定您期望在哪一行代码中出现异常。我希望这有帮助。
foo
是 final
,它会因为您不能代理 foo
而失败?
你也可以这样做:
@Test
public void testFooThrowsIndexOutOfBoundsException() {
try {
foo.doStuff();
assert false;
} catch (IndexOutOfBoundsException e) {
assert true;
}
}
Assert.fail()
而不是 assert
,以防您的测试在未启用断言的环境中运行。
恕我直言,在 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");
可能更可取。
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 的更多信息
expectThrows()
是 TestNG 的一部分,而不是 JUnit
我在 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("");
}
}
我在这里尝试了很多方法,但它们要么很复杂,要么不太符合我的要求。事实上,可以很简单地编写一个辅助方法:
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;并且适用于最终课程。
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!" );
}
就我而言,我总是从 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");
只需制作一个可以关闭和打开的 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();
在 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");
}
}
我们可以在必须返回异常的方法之后使用断言失败:
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
将吞噬堆栈跟踪,从而丢失有用的信息
除了 NamShubWriter 所说的之外,请确保:
ExpectedException 实例是公开的(相关问题)
ExpectedException 没有在 @Before 方法中实例化。这篇文章清楚地解释了 JUnit 执行顺序的所有复杂性。
不要这样做:
@Rule
public ExpectedException expectedException;
@Before
public void setup()
{
expectedException = ExpectedException.none();
}
最后,this 博文清楚地说明了如何断言某个异常被抛出。
使用 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)
解决方案在方法级别上进行测试。
我推荐库 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(".......);
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 中获得运行良好的版本。
举个例子,你想为下面提到的代码片段编写 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"});
}
使用 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);
好处:
不依赖任何库
本地化检查 - 更精确,如果需要,允许在一个测试中拥有多个这样的断言
便于使用
@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;
}
}
这是另一种检查方法是否抛出正确异常的方法。
不定期副业成功案例分享
get()
的数据结构是ArrayList
的事实是无关紧要的。如果我将来选择移动到原始数组,那么我将不得不更改此测试实现。应该隐藏数据结构,以便测试可以专注于 类 的行为。