ShouldThrow.java

package uk.org.lidalia.test;

import com.google.common.base.Optional;

import static uk.org.lidalia.lang.Exceptions.throwUnchecked;

/**
 * Static functions for returning an expected type of Throwable from a block of code for unit testing purposes.
 */
public final class ShouldThrow {

    private static final String INVOKER_INVOCATION_EXCEPTION_CLASSNAME = "org.codehaus.groovy.runtime.InvokerInvocationException";

    /**
     * Asserts that a code block throws a {@link Throwable} of the given type and returns it to permit further assertions on the
     * Throwable instance returned.
     *
     * @param expectedThrowableType the class of the throwable the code block should throw
     * @param workThatShouldThrowThrowable a code block that ought to throw the expected type when evaluated
     * @param <ThrowableType> the type of the throwable the code block should throw
     * @throws AssertionError if no Throwable is thrown at all whilst executing the code block
     * @throws Throwable any other Throwable thrown by the code block that is not an instance of the expected type
     * @return the instance of the expected throwable type thrown by the code block, if it was thrown
     */
    public static <ThrowableType extends Throwable> ThrowableType shouldThrow(
            final Class<ThrowableType> expectedThrowableType,
            final Runnable workThatShouldThrowThrowable) {
        return shouldThrow(expectedThrowableType, Optional.<String>absent(), workThatShouldThrowThrowable);
    }

    /**
     * Asserts that a code block throws a {@link Throwable} of the given type and returns it to permit further assertions on the
     * Throwable instance returned.
     *
     * @param expectedThrowableType the class of the throwable the code block should throw
     * @param message the message to be used as the message of the AssertionError if no throwable is thrown at all
     * @param workThatShouldThrowThrowable a code block that ought to throw the expected type when evaluated
     * @param <ThrowableType> the type of the throwable the code block should throw
     * @throws AssertionError if no Throwable is thrown at all whilst executing the code block
     * @throws Throwable any other Throwable thrown by the code block that is not an instance of the expected type
     * @return the instance of the expected throwable type thrown by the code block, if it was thrown
     */
    public static <ThrowableType extends Throwable> ThrowableType shouldThrow(
            final Class<ThrowableType> expectedThrowableType,
            final String message,
            final Runnable workThatShouldThrowThrowable) {
        return shouldThrow(expectedThrowableType, Optional.of(message), workThatShouldThrowThrowable);
    }

    @SuppressWarnings({ "unchecked", "PMD.AvoidCatchingThrowable" })
    private static <ThrowableType extends Throwable> ThrowableType shouldThrow(
            final Class<ThrowableType> expectedThrowableType,
            final Optional<String> message,
            final Runnable workThatShouldThrowThrowable) {
        try {
            workThatShouldThrowThrowable.run();
            throw new AssertionError(message.or("No exception thrown"));
        } catch (final Throwable actualThrowableThrown) {
            final Throwable trueThrowable = extractTrueThrowable(expectedThrowableType, actualThrowableThrown);
            if (!expectedThrowableType.isInstance(trueThrowable)) {
                throwUnchecked(actualThrowableThrown);
            }
            return (ThrowableType) trueThrowable;
        }
    }

    private static <ThrowableType extends Throwable> Throwable extractTrueThrowable(
            final Class<ThrowableType> expectedThrowableType,
            final Throwable actualThrowableThrown) {
        final Throwable trueThrowable;
        if (actualThrowableThrown.getClass().getName().equals(INVOKER_INVOCATION_EXCEPTION_CLASSNAME)
                && !expectedThrowableType.getName().equals(INVOKER_INVOCATION_EXCEPTION_CLASSNAME)) {
            trueThrowable = actualThrowableThrown.getCause();
        } else {
            trueThrowable = actualThrowableThrown;
        }
        return trueThrowable;
    }

    private ShouldThrow() {
        throw new UnsupportedOperationException("Not instantiable");
    }
}