Emulating ExpectedException with the command pattern
The code for this tutorial can be found here.
jUnit4
offers the pretty nifty class ExpectedException. By using the @Rule
annotation, this class allows us to write short unit tests that expect a certain exception type to be thrown, and exactly that exception type! For example, suppose you have a class Container
which takes an int argument in its constructor, providing the original size of the container. If the user supplies a negative integer, we want to throw an instance of IllegalArgumentException
.
Here is how ExpectedException
can help us:
@Rule public ExpectedException thrown= ExpectedException.none(); @Test public void ensureBadArgumentThrows(){ thrown.expect(IllegalArgumentException.class); new Container(-1); // Should throw IllegalArgumentException }
I recently had to work in an environment where jUnit
, and in particular Hamcrest
, had a largely broken installation. This means that I had to write my own testing routines and eschew the built-in jUnit
methods. This included situations such as this.
A reasonable solution is based on a try–catch block such as this:
Throwable t = null; try { methodThatShouldThrowAnIllegalArgumentException(); } catch(Throwable tThrown){ t = tThrown; } if(t == null || t.getClass() != IllegalArgumentException.class) System.out.println("Method did not throw expected exception!");
So let’s try to package this into a method of our own. We clearly need references to both the actual subclass of Throwable that we want, as well as the method to execute in order for the particular Throwable that we want to be thrown. This had me stumped at first, since I had never worked around with method references in Java.
No matter; we can do better. We will leverage the command pattern. Within our Main class, we can define an interface called Thrower, with a simple, void, arg-free method called throwIt()
:
private interface Thrower { void throwIt(); }
Now suppose that we only have two types of exceptions that we expect client code to throw, namely an ArrayIndexOutOfBoundsException
, and a NullPointerException
. For both exceptions, we will create a small class which will extend Thrower
and override throwIt()
appropriately:
private static class ThrowsArrayIndexOutOfBoundsException implements Thrower { @Override public void throwIt() { Object[] array = new Object[2]; Object temp = array[3]; // ArrayIndexOutOfBoundsException thrown. } } private static class ThrowsNullPointerException implements Thrower { @Override public void throwIt() { Object o = null; o.hashCode(); // NullPointerException thrown. } }
It’s almost like we are using Thrower
as a functional interface! We can now package the code snippet shared above in a method, which we will call expectThrowable()
:
private static boolean expectThrowable(Class<?> expectedClass, Thrower methodThatThrows) { Throwable exc = null; try { methodThatThrows.throwIt(); } catch(Throwable thrown){ exc = thrown; } return (exc != null && exc.getClass() == expectedClass); }
Naturally, we could also make this method void
and have it throw some other exception if our conditions are not met, but for simplicity let’s leave it as a boolean
method for this example.
Some example calls to this method:
public static void main(String[] args) { System.out.println(expectThrowable(ArrayIndexOutOfBoundsException.class, new ThrowsArrayIndexOutOfBoundsException())); // true System.out.println(expectThrowable(AssertionError.class, new ThrowsArrayIndexOutOfBoundsException())); // false System.out.println(expectThrowable(NullPointerException.class, new ThrowsNullPointerException())); // true System.out.println(expectThrowable(AssertionError.class, new ThrowsNullPointerException())); // false System.out.println(expectThrowable(Throwable.class, new ThrowsNullPointerException())); // false, too generic System.out.println(expectThrowable(RuntimeException.class, new ThrowsNullPointerException())); // false, also too generic }
So there you have it, one way to emulate ExpectedException with your own code. The way I see it, the bottlenecks here are the JVM’s set-up of the try–catch block and the upcastings in the main()
method (late binding is slow, but it’s all we have in Java). Neither are avoidable even in the implementation of ExpectedException
.