Java JUnit:可能“期望”一个包装异常?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/871216/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
JUnit: Possible to 'expect' a wrapped exception?
提问by ivan_ivanovich_ivanoff
I know that one can define an 'expected'exception in JUnit, doing:
我知道可以在 JUnit 中定义“预期”异常,执行以下操作:
@Test(expect=MyException.class)
public void someMethod() { ... }
But what if there is always same exception thrown, but with different 'nested' causes.
但是,如果总是抛出相同的异常,但具有不同的“嵌套” 原因呢?
Any suggestions?
有什么建议?
采纳答案by Paul Sonier
You could wrap the testing code in a try / catch block, catch the thrown exception, check the internal cause, log / assert / whatever, and then rethrow the exception (if desired).
您可以将测试代码包装在 try / catch 块中,捕获抛出的异常,检查内部原因,记录 / 断言 / 无论如何,然后重新抛出异常(如果需要)。
回答by GreenKiwi
You could always do it manually:
你总是可以手动完成:
@Test
public void someMethod() {
try{
... all your code
} catch (Exception e){
// check your nested clauses
if(e.getCause() instanceof FooException){
// pass
} else {
Assert.fail("unexpected exception");
}
}
回答by digitalbreed
I wrote a little JUnit extension for that purpose. A static helper function takes a function body and an array of expected exceptions:
我为此编写了一个小的 JUnit 扩展。静态辅助函数接受一个函数体和一组预期的异常:
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Arrays;
public class AssertExt {
public static interface Runnable {
void run() throws Exception;
}
public static void assertExpectedExceptionCause( Runnable runnable, @SuppressWarnings("unchecked") Class[] expectedExceptions ) {
boolean thrown = false;
try {
runnable.run();
} catch( Throwable throwable ) {
final Throwable cause = throwable.getCause();
if( null != cause ) {
assertTrue( Arrays.asList( expectedExceptions ).contains( cause.getClass() ) );
thrown = true;
}
}
if( !thrown ) {
fail( "Expected exception not thrown or thrown exception had no cause!" );
}
}
}
You can now check for expected nested exceptions like so:
您现在可以检查预期的嵌套异常,如下所示:
import static AssertExt.assertExpectedExceptionCause;
import org.junit.Test;
public class TestExample {
@Test
public void testExpectedExceptionCauses() {
assertExpectedExceptionCause( new AssertExt.Runnable(){
public void run() throws Exception {
throw new Exception( new NullPointerException() );
}
}, new Class[]{ NullPointerException.class } );
}
}
This saves you writing the same boiler plate code again and again.
这可以节省您一次又一次地编写相同的样板代码。
回答by mtpettyp
If you're using the latest version of JUnit you can extend the default test runner to handle this for you (without having to wrap each of your methods in a try/catch block)
如果您使用的是最新版本的 JUnit,您可以扩展默认测试运行器来为您处理此问题(无需将每个方法包装在 try/catch 块中)
ExtendedTestRunner.java - New test runner:
ExtendedTestRunner.java - 新的测试运行器:
public class ExtendedTestRunner extends BlockJUnit4ClassRunner
{
public ExtendedTestRunner( Class<?> clazz )
throws InitializationError
{
super( clazz );
}
@Override
protected Statement possiblyExpectingExceptions( FrameworkMethod method,
Object test,
Statement next )
{
ExtendedTest annotation = method.getAnnotation( ExtendedTest.class );
return expectsCauseException( annotation ) ?
new ExpectCauseException( next, getExpectedCauseException( annotation ) ) :
super.possiblyExpectingExceptions( method, test, next );
}
@Override
protected List<FrameworkMethod> computeTestMethods()
{
Set<FrameworkMethod> testMethods = new HashSet<FrameworkMethod>( super.computeTestMethods() );
testMethods.addAll( getTestClass().getAnnotatedMethods( ExtendedTest.class ) );
return testMethods;
}
@Override
protected void validateTestMethods( List<Throwable> errors )
{
super.validateTestMethods( errors );
validatePublicVoidNoArgMethods( ExtendedTest.class, false, errors );
}
private Class<? extends Throwable> getExpectedCauseException( ExtendedTest annotation )
{
if (annotation == null || annotation.expectedCause() == ExtendedTest.None.class)
return null;
else
return annotation.expectedCause();
}
private boolean expectsCauseException( ExtendedTest annotation) {
return getExpectedCauseException(annotation) != null;
}
}
ExtendedTest.java - annotation to mark test methods with:
ExtendedTest.java - 用于标记测试方法的注释:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface ExtendedTest
{
/**
* Default empty exception
*/
static class None extends Throwable {
private static final long serialVersionUID= 1L;
private None() {
}
}
Class<? extends Throwable> expectedCause() default None.class;
}
ExpectCauseException.java - new JUnit Statement:
ExpectCauseException.java - 新的 JUnit 语句:
public class ExpectCauseException extends Statement
{
private Statement fNext;
private final Class<? extends Throwable> fExpected;
public ExpectCauseException( Statement next, Class<? extends Throwable> expected )
{
fNext= next;
fExpected= expected;
}
@Override
public void evaluate() throws Exception
{
boolean complete = false;
try {
fNext.evaluate();
complete = true;
} catch (Throwable e) {
if ( e.getCause() == null || !fExpected.isAssignableFrom( e.getCause().getClass() ) )
{
String message = "Unexpected exception cause, expected<"
+ fExpected.getName() + "> but was<"
+ ( e.getCause() == null ? "none" : e.getCause().getClass().getName() ) + ">";
throw new Exception(message, e);
}
}
if (complete)
throw new AssertionError( "Expected exception cause: "
+ fExpected.getName());
}
}
Usage:
用法:
@RunWith( ExtendedTestRunner.class )
public class MyTests
{
@ExtendedTest( expectedCause = MyException.class )
public void someMethod()
{
throw new RuntimeException( new MyException() );
}
}
回答by Jesse Merriman
You could create a Matcherfor exceptions. This works even when you are using another test runner like Arquillian's @RunWith(Arquillian.class)
so you can't use the @RunWith(ExtendedTestRunner.class)
approach suggested above.
您可以为异常创建一个匹配器。即使您使用另一个测试运行器(如Arquillian的),这也有效,@RunWith(Arquillian.class)
因此您不能使用@RunWith(ExtendedTestRunner.class)
上面建议的方法。
Here's a simple example:
这是一个简单的例子:
public class ExceptionMatcher extends BaseMatcher<Object> {
private Class<? extends Throwable>[] classes;
// @SafeVarargs // <-- Suppress warning in Java 7. This usage is safe.
public ExceptionMatcher(Class<? extends Throwable>... classes) {
this.classes = classes;
}
@Override
public boolean matches(Object item) {
for (Class<? extends Throwable> klass : classes) {
if (! klass.isInstance(item)) {
return false;
}
item = ((Throwable) item).getCause();
}
return true;
}
@Override
public void describeTo(Description descr) {
descr.appendText("unexpected exception");
}
}
Then use it with @Ruleand ExpectedExceptionlike this:
然后将它与@Rule和ExpectedException一起使用,如下所示:
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void testSomething() {
thrown.expect(new ExceptionMatcher(IllegalArgumentException.class, IllegalStateException.class));
throw new IllegalArgumentException("foo", new IllegalStateException("bar"));
}
Added by Craig Ringer in 2012 edit:An enhanced and more reliable version:
由 Craig Ringer 在 2012 年编辑添加:增强且更可靠的版本:
- Basic usage unchanged from above
- Can pass optional 1st argument
boolean rethrow
to throw unmatched exception. That preserves the stack trace of the nested exceptions for easier debugging. - Uses Apache Commons LangExceptionUtils to handle cause loops and to handle non-standard exception nesting used by some common exception classes.
- Self-describe includes accepted exceptions
- Self-describe on failure includes a the cause stack of the exception encountered
- Handle Java 7 warning. Remove the
@SaveVarargs
on older versions.
- 基本用法不变
- 可以传递可选的第一个参数
boolean rethrow
以抛出不匹配的异常。这保留了嵌套异常的堆栈跟踪以便于调试。 - 使用Apache Commons LangExceptionUtils 来处理原因循环和处理一些常见异常类使用的非标准异常嵌套。
- 自我描述包括可接受的例外
- 失败的自我描述包括遇到异常的原因堆栈
- 处理 Java 7 警告。删除
@SaveVarargs
旧版本上的。
Full code:
完整代码:
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
public class ExceptionMatcher extends BaseMatcher<Object> {
private Class<? extends Throwable>[] acceptedClasses;
private Throwable[] nestedExceptions;
private final boolean rethrow;
@SafeVarargs
public ExceptionMatcher(Class<? extends Throwable>... classes) {
this(false, classes);
}
@SafeVarargs
public ExceptionMatcher(boolean rethrow, Class<? extends Throwable>... classes) {
this.rethrow = rethrow;
this.acceptedClasses = classes;
}
@Override
public boolean matches(Object item) {
nestedExceptions = ExceptionUtils.getThrowables((Throwable)item);
for (Class<? extends Throwable> acceptedClass : acceptedClasses) {
for (Throwable nestedException : nestedExceptions) {
if (acceptedClass.isInstance(nestedException)) {
return true;
}
}
}
if (rethrow) {
throw new AssertionError(buildDescription(), (Throwable)item);
}
return false;
}
private String buildDescription() {
StringBuilder sb = new StringBuilder();
sb.append("Unexpected exception. Acceptable (possibly nested) exceptions are:");
for (Class<? extends Throwable> klass : acceptedClasses) {
sb.append("\n ");
sb.append(klass.toString());
}
if (nestedExceptions != null) {
sb.append("\nNested exceptions found were:");
for (Throwable nestedException : nestedExceptions) {
sb.append("\n ");
sb.append(nestedException.getClass().toString());
}
}
return sb.toString();
}
@Override
public void describeTo(Description description) {
description.appendText(buildDescription());
}
}
Typical output:
典型输出:
java.lang.AssertionError: Expected: Unexpected exception. Acceptable (possibly nested) exceptions are:
class some.application.Exception
Nested exceptions found were:
class javax.ejb.EJBTransactionRolledbackException
class javax.persistence.NoResultException
got: <javax.ejb.EJBTransactionRolledbackException: getSingleResult() did not retrieve any entities.>
回答by rwitzel
The most concise syntax is provided by catch-exception:
catch-exception提供了最简洁的语法:
import static com.googlecode.catchexception.CatchException.*;
catchException(myObj).doSomethingNasty();
assertTrue(caughtException().getCause() instanceof MyException);
回答by Rowan
As of JUnit 4.11 you can use the ExpectedException
rule's expectCause()
method:
从 JUnit 4.11 开始,您可以使用ExpectedException
规则的expectCause()
方法:
import static org.hamcrest.CoreMatchers.*;
// ...
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Test
public void throwsNestedException() throws Exception {
expectedException.expectCause(isA(SomeNestedException.class));
throw new ParentException("foo", new SomeNestedException("bar"));
}