2009-05-15 2 views
30

Я знаю, что можно определить «ожидаемого» исключения в JUnit, делать:JUnit: Можно ли ожидать «обернутого исключения»?

@Test(expect=MyException.class) 
public void someMethod() { ... } 

Но что, если всегда есть же исключение брошено, но с разными «вложенными» причинами.

Любые предложения?

+1

неважным боковой примечание: это «ожидается = ...», а не «ожидать = ...» – hoijui

+2

Я не могу поверить, что JUnit 5, по-видимому, не дополненная его синтаксис аннотаций включить это. –

ответ

21

Вы можете обернуть тестовый код в блок try/catch, поймать заброшенное исключение, проверить внутреннюю причину, log/assert/whatever, а затем переустановить исключение (при желании).

5

Вы всегда можете сделать это вручную:

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

Я написал небольшое расширение 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!"); 
     } 
    } 
} 

Теперь вы можете проверить ожидаемые вложенные исключения следующим образом:

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

Это экономит писать снова тот же код котла пластины и опять.

+1

Было бы хорошо, если бы у java было закрытие! Как и в случае, try/catch/getCause(), вероятно, меньше кода плиты котла, чем разработка анонимных классов! –

7

Если вы используете последнюю версию JUnit вы можете продлить тест по умолчанию бегун на, чтобы справиться с этим для вас (без завернуть каждого из методов в Try/поймать блок)

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 - аннотация помечать методы испытаний с:

@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 - новый 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()); 
    } 
} 

Использование:

@RunWith(ExtendedTestRunner.class) 
public class MyTests 
{ 
    @ExtendedTest(expectedCause = MyException.class) 
    public void someMethod() 
    { 
     throw new RuntimeException(new MyException()); 
    } 
} 
+0

Мне нравится это решение! Однако, к сожалению, у меня возникли проблемы с его компиляцией в сочетании с тестированием Groovy JUnit 4. –

+0

Это самое чистое решение. Пара исправлений: ExtendedTestRunner необходимо расширить SpringJUnit4ClassRunner для правильной поддержки контекста Spring. Также computeTestMethods имеет несовместимый тип возврата (должен быть ArrayList). – warden

4

Вы можете создать Matcher исключений. Это работает даже тогда, когда вы используете другой тестовый бегун, например Arquillian, @RunWith(Arquillian.class), поэтому вы не можете использовать предложенный выше подход @RunWith(ExtendedTestRunner.class).

Вот простой пример:

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

Затем используйте его с @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")); 
} 

Добавил Craig Ringer в 2012 редактирования: Усовершенствованная и более достоверная версия:

  • Основная информация о США ge неизменным сверху
  • Может передать необязательный 1-й аргумент boolean rethrow, чтобы выбросить непревзойденное исключение. Это сохраняет трассировку стека вложенных исключений для упрощения отладки.
  • Использование Apache Commons Lang ExceptionUtils для обработки циклов обработки и обработки нестандартного исключения, используемого некоторыми общими классами исключений.
  • Self-описать включает принимаются исключения
  • Self-описания на отказ включает в себя стек причиной исключения встречаются
  • Handle Java 7 предупреждение. Удалите @SaveVarargs в старых версиях.

Полный код:

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

} 

Типичный выход:

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.> 
+0

Отличный и очень полезный ответ - спасибо. Этот подход является спасателем при работе с Arquillian для тестирования EJB, так как им нравится обертывать каждое исключенное исключение в исключение EJBException. –

+0

Я распространил пример в ответе на что-то более полное. –

1

Наиболее кратким синтаксис обеспечивается catch-exception:

import static com.googlecode.catchexception.CatchException.*; 

catchException(myObj).doSomethingNasty(); 
assertTrue(caughtException().getCause() instanceof MyException); 
66

По 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")); 
} 
+6

из-за хаоса атак червя, строка 6 должна выглядеть так: 'expectedException.expectCause (is IsInstanceOf. instanceOf (SomeNestedException.class)));' , но кроме этого это элегантное решение. – thrau

+1

Решение @ thrau работает для меня без дополнительного: 'expectedException.expectCause (IsInstanceOf. instanceOf (SomeNestedE xception.class));' – Jardo

+0

@Jardo Да, это правильно - is() - это просто синтаксический сахар, который проходит до вложенный матчер. См. Документы здесь: http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/core/Is.html#is(org.hamcrest.Matcher) – Rowan

Смежные вопросы