44

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?

4
  • 1
    Twould appear that you're right and it cannot catch generics. Lame stackoverflow.com/questions/2444633/… Commented Sep 13, 2013 at 20:36
  • Dangit, Java. This is just yet another of many hurdles that you throw in our path while trying to make code the slightest bit clean using Java 8's lambdas. Commented Jan 19, 2015 at 11:27
  • I have added an answer where I have reified a Java try/catch statement into my implementation of an Either<L, R> via the specialization of, Either<L extends Throwable, R>, returned via either of the pair of tryCatch() methods: stackoverflow.com/a/79303726/501113 Commented Dec 23, 2024 at 16:57
  • I now have added a super simple answer: stackoverflow.com/a/79815838/501113 Commented Nov 10 at 16:15

9 Answers 9

39

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.

Sign up to request clarification or add additional context in comments.

5 Comments

You should probably include a full usage example so people can see how it's no simpler or more readable than the standard idiom!
@Pete Kirkham: isAssignableFrom takes a Class as a parameter, and essentially does the same thing as isInstance does on an object. isInstance returns true if the argument is assignment-compatible to the class, so it works on subclasses equally well.
@Kevin Bourillion: I'm more interested in just the technical possibilities than advocating one way or another. I think your post correctly addresses the hazards of such an approach, and already upvoted you accordingly.
While I agree with @KevinBourrillion 's sentiment, I really don't like cutting and pasting the exact same try catch handler code every time I expect an exception. I came up with a solution very much like this one, except I also assert on the error message so I know exactly which specific error was thrown. I can accept the slight loss of clarity for a standardized pattern which is easy to identify and much nicer to code.
maybe throw new RuntimeException(ex) instead, for those of us that can't throw Exception...
3

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.

3 Comments

Maybe I'm wrong but I think JUnit is pretty specific in its design and that within it, @ExpectedException isn't a too-clever solution. If your tests are so complicated that you don't know where your exceptions could be coming from then you aren't using JUnit right. They would say you might want to look at refactoring your code to better define what each of your classes/methods are doing. Given a good modular design I don't see why this should be an issue. Are there issues to this I am missing or understating?
I get that you wrote this 14 years ago. However, there has been quite a bit of progress in FP around containing the exception model inside of a proper class such as Either<L extends Throwable, R> kind of type. I have detailed that approach in my Answer: stackoverflow.com/a/79303726/501113
This is kind of a BS answer, particularly coming from a Googler. If large SDK players like Google and Amazon (AWS) would do the right thing and develop an exception framework that did things like derive common exception classes like throttling exceptions properly from a base class, this "cure" wouldn't be needed. It drives me crazy when 400 SDK API classes all have their own duplicative exception classes. Specialization is fine, duplication is a terrible approach.
3

Catch clause with type parameter is not possible:
http://docs.oracle.com/javase/tutorial/java/generics/restrictions.html#cannotCatch

Comments

0

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.

Comments

0

Below is a simpler pattern that I finally figured out. And then wrote in more detail about here.

The following code fails to compile with an error of Unhandled exception: java.lang.Exception.

var arrayCharsX100 = new char[100];
Function<StringReader, Integer> lambdaWontCompile =
    (stringReader) ->
        stringReader.read(arrayCharsX100);
var charsReadCountWontCompile = lambdaWontCompile.apply(
    new StringReader("placeholder won’t compile"));

By adding the following to my code base...

@FunctionalInterface
public interface FunctionCheckedException<T, R> {

  R apply(T t) throws Exception;

}

public static <T, R> Function<T, R> wrapCheckedException(
    FunctionCheckedException<T, R> functionCheckedException
) {
  return (T t) -> {
    try {
      return functionCheckedException.apply(t);
    } catch (RuntimeException runtimeException) {
      throw runtimeException;
    } catch (Exception exception) {
      throw new RuntimeException("wrapping a checked exception", exception);
    }
  };
}

... the code will now compile once I wrap the function/lambda/closure with wrapCheckedException, like this:

var arrayCharsX100 = new char[100];
Function<StringReader, Integer> lambdaWillCompile =
    wrapCheckedException(
        (stringReader) ->
            stringReader.read(arrayCharsX100));
var charsReadCountWillCompile = lambdaWillCompile.apply(
    new StringReader("placeholder compiles"));

Comments

0
@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);
                    }
                }
            };
        }
  1. Usage example
Stream.of("a")
    .forEach(
       errConsumerWrapper(
           i -> Integer.parseInt(i),
           NumberFormatException.class,
           Throwable::printStackTrace));

1 Comment

I like how simple this is. I tried to make it even simpler here: stackoverflow.com/a/79815838/501113
-1

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.

3 Comments

I think for testing purposes it could have value.
MarkPeters' answer is roughly what I was going for. Though it still has the whole "expected successful execution path requires exceptions, while error case doesn't." It also changes the execution slightly, so unexpected exceptions aren't rethrown, but instead trigger a call to fail. In your original code, you had three paths, expected exception, unexpected exception which goes up the stack, and no exception at all, calling fail. The Mark's code simplifies to expected exception and calling fail for unexpected exceptions and no exception cases.
Sorry, started my comment before you added yours. There is a potential use in testing, but I wanted to caution just in case this was for production code. Modern exception handling optimizes for the no exception case, so under success circumstances you'll be incurring overhead that your failure case avoids. Perverse, to say the least.
-1

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.

Comments

-1

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.

like this

like this

1 Comment

I do use IDEA but want to extract the common code into something that expresses the intent nicely

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.