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

Я пишу несколько тестов 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(класс), который делает то, что я хочу - но я не использую Spring.)

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

Ответы

Ответ 1

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

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

Имитация - это самая искренняя форма лести. Основываясь на быстрой проверке источника, это именно то, что делает 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;
    }
}

CAVEAT. Вышеприведенный код относится к 4 марта 2009 года, поэтому, если вы действительно хотите знать, что делает Spring прямо сейчас, вам следует исследовать код, как он существует сегодня (всякий раз то есть).

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

На основании ответа Патрика Бооса: Если вы используете 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);