Reputation: 50094
We use JUnit 3 at work and there is no ExpectedException
annotation. I wanted to add a utility to our code to wrap this:
try {
someCode();
fail("some error message");
} catch (SomeSpecificExceptionType ex) {
}
So I tried this:
public static class ExpectedExceptionUtility {
public static <T extends Exception> void checkForExpectedException(String message, ExpectedExceptionBlock<T> block) {
try {
block.exceptionThrowingCode();
fail(message);
} catch (T ex) {
}
}
}
However, Java cannot use generic exception types in a catch block, I think.
How can I do something like this, working around the Java limitation?
Is there a way to check that the ex
variable is of type T
?
Upvotes: 44
Views: 46241
Reputation: 5934
If you are interested in the OOP+FP way of approaching this, there is a specific type, Either<L, R>
, where a specialization can be created as Either<L extends Throwable, R>
via a .tryCatch()
method.
While it is too much to show the entire implementation of Either<L, R>
in this answer (a fully documented implementation is located in this Gist), below is a paraphrasing snapshot of what it looks like to reify any descendant of Throwable
into a proper capture filter.
To see the implementation details, specifically focus on the pair of tryCatch()
methods. This StackOverflow Answer inspired the first of these two methods.
public final class Either<L, R> {
public static <L, R> Either<L, R> left(L value) {
return new Either<>(Optional.of(value), Optional.empty());
}
public static <L, R> Either<L, R> right(R value) {
return new Either<>(Optional.empty(), Optional.of(value));
}
public static <L extends Throwable, R> Either<L, R> tryCatch(
Supplier<R> successSupplier,
Class<L> throwableType
) {
try {
return Either.right(Objects.requireNonNull(successSupplier.get()));
} catch (Throwable throwable) {
if (throwableType.isInstance(throwable)) {
return Either.left((L) throwable);
}
throw throwable;
}
}
public static <R> Either<RuntimeException, R> tryCatch(Supplier<R> successSupplier) {
return tryCatch(successSupplier, RuntimeException.class);
}
private final Optional<L> left;
private final Optional<R> right;
private Either(Optional<L> left, Optional<R> right) {
if (left.isEmpty() == right.isEmpty()) {
throw new IllegalArgumentException("left.isEmpty() must not be equal to right.isEmpty()");
}
this.left = left;
this.right = right;
}
@Override
public boolean equals(Object object) { ... }
@Override
public int hashCode() { ... }
public boolean isLeft() { ... }
public boolean isRight() { ... }
public L getLeft() { ... }
public R getRight() { ... }
public Optional<R> toOptional() { ... }
public <T> Either<L, T> map(Function<? super R, ? extends T> rightFunction) { ... }
public <T> Either<L, T> flatMap(Function<? super R, ? extends Either<L, ? extends T>> rightFunction) { ... }
public <T> Either<T, R> mapLeft(Function<? super L, ? extends T> leftFunction) { ... }
public <T> Either<L, T> mapRight(Function<? super R, ? extends T> rightFunction) { ... }
public <T> Either<T, R> flatMapLeft(Function<? super L, ? extends Either<? extends T, R>> leftFunction) { ... }
public <T> Either<L, T> flatMapRight(Function<? super R, ? extends Either<L, ? extends T>> rightFunction) { ... }
public <T> T converge(Function<? super L, ? extends T> leftFunction, Function<? super R, ? extends T> rightFunction) { ... }
public <T> T converge() { ... }
public static <T> T converge(Either<? extends T, ? extends T> either) { ... }
public void forEach(Consumer<? super L> leftAction, Consumer<? super R> rightAction) { ... }
}
A quick way to see the above code in action is to check out this validation suite of JUnit tests.
Upvotes: 0
Reputation: 17
@FunctionalInterface
public interface ThrowingConsumer<T, E extends Exception> {
void accept(T t) throws E;
}
public static <T, E extends Exception> Consumer<T> errConsumerWrapper(ThrowingConsumer<T, E> throwingConsumer,
Class<E> exceptionClass,
Consumer<E> exceptionConsumer) {
return i -> {
try {
throwingConsumer.accept(i);
} catch (Exception ex) {
try {
exceptionConsumer.accept(exceptionClass.cast(ex));
} catch (ClassCastException ccEx) {
throw new RuntimeException(ex);
}
}
};
}
Usage example
Stream.of("a").forEach(errConsumerWrapper(i -> Integer.parseInt(i), NumberFormatException.class, Throwable::printStackTrace));
Upvotes: -1
Reputation: 199333
You can also use an IDE that supports live template ( like IntellJ IDEA for instance ) and assign a shortcut like ee -> [tab]
that inserts the try/catch/ignore for your and let you type the correct one.
Upvotes: -1
Reputation: 37
Generics are not types. They are not templates. They are compile time type checks, in Java. Exception blocks catch on type. You can catch(Exception e) or even catch(Throwable e) and then cast as needed.
Upvotes: -1
Reputation: 2202
Catch clause with type parameter is not possible:
http://docs.oracle.com/javase/tutorial/java/generics/restrictions.html#cannotCatch
Upvotes: 3
Reputation: 40851
I understand the impulse to try to simplify your exception-test idiom, but seriously: don't. Every possible choice you'll come up with is a cure that's worse than the disease. Especially JUnit 4's @ExpectedException nonsense! It is a too-clever frameworky solution, requiring everyone to learn how it works, as opposed to a plain self-evident bit of regular Java code. Worse, it gives you no way to wrap only the part of your test that you expect to throw the exception, so if an earlier setup step throws that same exception, your test will pass even though your code is broken.
I could write a long diatribe about this here (I'm sorry for not having enough time to), as we've had a lengthy discussion of this issue among Java engineers here at Google, and the consensus was that none of these crazy solutions are worthwhile. Get used to try/catch, it's really not that bad.
Upvotes: 3
Reputation: 81164
You could pass the Class object in and check that programatically.
public static <T extends Exception> void checkForException(String message,
Class<T> exceptionType, ExpectedExceptionBlock<T> block) {
try {
block.exceptionThrowingCode();
} catch (Exception ex) {
if ( exceptionType.isInstance(ex) ) {
return;
} else {
throw ex; //optional?
}
}
fail(message);
}
//...
checkForException("Expected an NPE", NullPointerException.class, //...
I'm not sure if you'd want the rethrow or not; rethrowing would equally fail/error the test but semantically I wouldn't, since it basically means "we didn't get the exception we expected" and so that represents a programming error, instead of a test environment error.
Upvotes: 38
Reputation: 155684
Well, you could just catch Exception and rethrow if it's not an expected Exception. Though good coding practice usually dictates that the success path of code should not be defined by an Exception, so you might want to rethink your design.
Upvotes: -1