在JUnit中,有许多方法可以在测试代码中测试异常,包括try-catch idiom
JUnit @Rule
和catch-exception
库。 从Java 8开始,我们还有另一种处理异常的方法:使用lambda表达式。 在这篇简短的博客文章中,我将演示一个简单的示例,说明如何利用Java 8和lambda表达式的功能来测试JUnit中的异常。
注意:撰写此博客文章的动机是在catch-exception
项目页面上发布的消息:
Java 8的lambda表达式将使catch-exception冗余。 因此,该项目将不再维护
SUT –被测系统
我们将测试以下2类抛出的异常。
第一个:
class DummyService {
public void someMethod() {
throw new RuntimeException("Runtime exception occurred");
}
public void someOtherMethod() {
throw new RuntimeException("Runtime exception occurred",
new IllegalStateException("Illegal state"));
}
}
第二个:
class DummyService2 {
public DummyService2() throws Exception {
throw new Exception("Constructor exception occurred");
}
public DummyService2(boolean dummyParam) throws Exception {
throw new Exception("Constructor exception occurred");
}
}
所需语法
我的目标是实现与catch-exception
库接近的语法:
package com.github.kolorobot.exceptions.java8;
import org.junit.Test;
import static com.github.kolorobot.exceptions.java8.ThrowableAssertion.assertThrown;
public class Java8ExceptionsTest {
@Test
public void verifiesTypeAndMessage() {
assertThrown(new DummyService()::someMethod) // method reference
// assertions
.isInstanceOf(RuntimeException.class)
.hasMessage("Runtime exception occurred")
.hasNoCause();
}
@Test
public void verifiesCauseType() {
assertThrown(() -> new DummyService().someOtherMethod(true)) // lambda expression
// assertions
.isInstanceOf(RuntimeException.class)
.hasMessage("Runtime exception occurred")
.hasCauseInstanceOf(IllegalStateException.class);
}
@Test
public void verifiesCheckedExceptionThrownByDefaultConstructor() {
assertThrown(DummyService2::new) // constructor reference
// assertions
.isInstanceOf(Exception.class)
.hasMessage("Constructor exception occurred");
}
@Test
public void verifiesCheckedExceptionThrownConstructor() {
assertThrown(() -> new DummyService2(true)) // lambda expression
// assertions
.isInstanceOf(Exception.class)
.hasMessage("Constructor exception occurred");
}
@Test(expected = ExceptionNotThrownAssertionError.class) // making test pass
public void failsWhenNoExceptionIsThrown() {
// expected exception not thrown
assertThrown(() -> System.out.println());
}
}
注意:与catch-exception
相比的优势在于,我们将能够测试引发异常的构造函数。
创建“图书馆”
合成糖
assertThrown
是一个静态工厂方法,它使用对捕获的异常的引用来创建ThrowableAssertion
的新实例。
package com.github.kolorobot.exceptions.java8;
public class ThrowableAssertion {
public static ThrowableAssertion assertThrown(ExceptionThrower exceptionThrower) {
try {
exceptionThrower.throwException();
} catch (Throwable caught) {
return new ThrowableAssertion(caught);
}
throw new ExceptionNotThrownAssertionError();
}
// other methods omitted for now
}
ExceptionThrower
是一个@FunctionalInterface
,可以使用lambda表达式,方法引用或构造函数引用创建实例。 assertThrown
接受ExceptionThrower
将期望并准备处理异常。
@FunctionalInterface
public interface ExceptionThrower {
void throwException() throws Throwable;
}
断言
最后,我们需要创建一些断言,以便可以在测试代码中验证关于teste异常的解释。 实际上, ThrowableAssertion
是一种自定义断言,为我们提供了一种有效地验证所捕获异常的方法。 在下面的代码中,我使用了Hamcrest
匹配器来创建断言。 ThrowableAssertion
类的完整来源:
package com.github.kolorobot.exceptions.java8;
import org.hamcrest.Matchers;
import org.junit.Assert;
public class ThrowableAssertion {
public static ThrowableAssertion assertThrown(ExceptionThrower exceptionThrower) {
try {
exceptionThrower.throwException();
} catch (Throwable caught) {
return new ThrowableAssertion(caught);
}
throw new ExceptionNotThrownAssertionError();
}
private final Throwable caught;
public ThrowableAssertion(Throwable caught) {
this.caught = caught;
}
public ThrowableAssertion isInstanceOf(Class<? extends Throwable> exceptionClass) {
Assert.assertThat(caught, Matchers.isA((Class<Throwable>) exceptionClass));
return this;
}
public ThrowableAssertion hasMessage(String expectedMessage) {
Assert.assertThat(caught.getMessage(), Matchers.equalTo(expectedMessage));
return this;
}
public ThrowableAssertion hasNoCause() {
Assert.assertThat(caught.getCause(), Matchers.nullValue());
return this;
}
public ThrowableAssertion hasCauseInstanceOf(Class<? extends Throwable> exceptionClass) {
Assert.assertThat(caught.getCause(), Matchers.notNullValue());
Assert.assertThat(caught.getCause(), Matchers.isA((Class<Throwable>) exceptionClass));
return this;
}
}
AssertJ实施
如果您使用AssertJ
库,则可以使用AssertJ
轻松创建ThrowableAssertion
AssertJ
版本,它提供了许多org.assertj.core.api.ThrowableAssert
断言。 该类的实现比上面介绍的Hamcrest
更简单。
package com.github.kolorobot.exceptions.java8;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.ThrowableAssert;
public class AssertJThrowableAssert {
public static ThrowableAssert assertThrown(ExceptionThrower exceptionThrower) {
try {
exceptionThrower.throwException();
} catch (Throwable throwable) {
return Assertions.assertThat(throwable);
}
throw new ExceptionNotThrownAssertionError();
}
}
AssertJ
的示例测试:
public class AssertJJava8ExceptionsTest {
@Test
public void verifiesTypeAndMessage() {
assertThrown(new DummyService()::someMethod)
.isInstanceOf(RuntimeException.class)
.hasMessage("Runtime exception occurred")
.hasMessageStartingWith("Runtime")
.hasMessageEndingWith("occurred")
.hasMessageContaining("exception")
.hasNoCause();
}
}
摘要
仅用几行代码,我们构建了非常酷的代码,可帮助我们在JUnit中测试异常,而无需任何其他库。 这仅仅是一个开始。 利用Java 8和lambda表达式的强大功能!