Лучший способ проверить, является ли определенный тип исключения причиной (причины и т. Д.) В вложенном исключении?

Я пишу некоторые тесты JUnit, которые проверяют, что исключение типа MyCustomException брошен Однако это исключение включается в другие исключения несколько раз, например, в InvocationTargetException, который, в свою очередь, помещается в RuntimeException.

Как лучше всего определить, вызвало ли MyCustomException исключение, которое я на самом деле ловлю? Я хотел бы сделать что-то вроде этого (см. Подчеркнутый):


try {
    doSomethingPotentiallyExceptional();
    fail("Expected an exception.");
} catch (RuntimeException e) {
     if (!e.wasCausedBy(MyCustomException.class)
        fail("Expected a different kind of exception.");
}

Я хотел бы избежать звонка getCause() несколько "слоев" глубоких и подобных уродливых обходных путей. Есть ли лучший способ?

(Очевидно, у Spring есть NestedRuntimeException.contains (Class), который делает то, что я хочу - но я не использую Spring.)

ЗАКРЫТО: ОК, я думаю, что на самом деле обойтись без утилитарного метода:-) Спасибо всем, кто ответил!

9 ответов

Решение

Почему вы хотите избежать getCause, Конечно, вы можете написать себе метод для выполнения задачи, например:

public static boolean isCause(
    Class<? extends Throwable> expected,
    Throwable exc
) {
   return expected.isInstance(exc) || (
       exc != null && isCause(expected, exc.getCause())
   );
}

Если вы используете Apache Commons Lang, то вы можете использовать следующее:

(1) Когда причина должна быть точно указанного типа

if (ExceptionUtils.indexOfThrowable(exception, ExpectedException.class) != -1) {
    // exception is or has a cause of type ExpectedException.class
}

(2) Когда причина должна быть либо указанного типа, либо типа его подкласса

if (ExceptionUtils.indexOfType(exception, ExpectedException.class) != -1) {
    // exception is or has a cause of type ExpectedException.class or its subclass
}

Я не думаю, что у вас есть выбор, кроме как вызвать через слои getCause. Если вы посмотрите на исходный код исключительной ситуации Spring NestedRuntimeException, о которой вы упомянули, это то, как она реализована.

Основываясь на ответе Патрика Бооса: Если вы используете Apache Commons Lang 3, вы можете проверить:

indexOfThrowable: возвращает индекс (на основе нуля) первого Throwable, который точно соответствует указанному классу в цепочке исключений. Подклассы указанного класса не совпадают

if (ExceptionUtils.indexOfThrowable(e, clazz) != -1) {
    // your code
}

или же

indexOfType: Возвращает (основанный на нуле) индекс первого Throwable, который соответствует указанному классу или подклассу в цепочке исключений. Подклассы указанного класса совпадают

if (ExceptionUtils.indexOfType(e, clazz) != -1) {
    // your code
}

Пример для нескольких типов с Java 8:

Class<? extends Throwable>[] classes = {...}
boolean match = Arrays.stream(classes)
            .anyMatch(clazz -> ExceptionUtils.indexOfType(e, clazz) != -1);

Вы можете сделать это с помощью гуавы:

FluentIterable.from(Throwables.getCausalChain(e))
                        .filter(Predicates.instanceOf(ConstraintViolationException.class))
                        .first()
                        .isPresent();

Подражание - это самая искренняя форма лести. Основываясь на быстрой проверке источника, именно это и делает NestedRuntimeException:

/**
 * Check whether this exception contains an exception of the given type:
 * either it is of the given class itself or it contains a nested cause
 * of the given type.
 * @param exType the exception type to look for
 * @return whether there is a nested exception of the specified type
 */
public boolean contains(Class exType) {
    if (exType == null) {
        return false;
    }
    if (exType.isInstance(this)) {
        return true;
    }
    Throwable cause = getCause();
    if (cause == this) {
        return false;
    }
    if (cause instanceof NestedRuntimeException) {
        return ((NestedRuntimeException) cause).contains(exType);
    }
    else {
        while (cause != null) {
            if (exType.isInstance(cause)) {
                return true;
            }
            if (cause.getCause() == cause) {
                break;
            }
            cause = cause.getCause();
        }
        return false;
    }
}

ПРЕДУПРЕЖДЕНИЕ. Выше приведен код от 4 марта 2009 года, поэтому, если вы действительно хотите узнать, что Spring делает прямо сейчас, вам следует изучить код в том виде, в каком он существует сегодня (когда бы он ни был).

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

public class ExceptionUtils {
     public static boolean wasCausedBy(Throwable e, Class<? extends Throwable>) {
         // call getCause() until it returns null or finds the exception
     }
}

Вы можете использовать ExceptionUtils.hasCause(ex, type).

Если интересующее исключение определенно является "основной" причиной, assertj это дополнительное место, чтобы найти getRootCause check, хотя из источника сегодня, похоже, возможная проблема с бесконечным циклом обсуждается в других ответах.

Другие вопросы по тегам