41

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?

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
Alex Baranosky
  • 48,865
  • 44
  • 102
  • 150
  • Twould appear that you're right and it *cannot* catch generics. Lame http://stackoverflow.com/questions/2444633/why-doesnt-java-support-generic-throwables – rogerdpack Sep 13 '13 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. – Kenogu Labz Jan 19 '15 at 11:27

7 Answers7

35

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.

Mark Peters
  • 80,126
  • 17
  • 159
  • 190
  • You should probably include a full usage example so people can see how it's no simpler or more readable than the standard idiom! – Kevin Bourrillion Jun 11 '10 at 17:04
  • @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. – Mark Peters Jun 11 '10 at 17:06
  • @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. – Mark Peters Jun 11 '10 at 17:08
  • 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. – Marquee Jul 01 '13 at 16:15
  • maybe throw new RuntimeException(ex) instead, for those of us that can't throw Exception... – rogerdpack Sep 13 '13 at 20:36
4

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.

Kevin Bourrillion
  • 40,336
  • 12
  • 74
  • 87
  • 4
    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? – Andrew Hubbs Jun 11 '10 at 17:25
1

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

oldo
  • 2,092
  • 1
  • 15
  • 11
0

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.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • I think for testing purposes it could have value. – Mark Peters Jun 11 '10 at 17:00
  • 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. – ShadowRanger Jun 11 '10 at 17:05
  • 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. – ShadowRanger Jun 11 '10 at 17:07
0

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

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
OscarRyz
  • 196,001
  • 113
  • 385
  • 569
0

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.

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));

aolo23
  • 19
  • 1
  • 2