In JUnit there are many ways of testing exceptions in test code, including try-catch idiom , JUnit @Rule , with catch-exception library. As of Java 8 we have another way of dealing with exceptions: with lambda expressions. In this short blog post I will demonstrate a simple example how one can utilize the power of Java 8 and lambda expressions to test exceptions in JUnit.

Note: The motivation for writing this blog post was the message published on the catch-exception project page:

Java 8’s lambda expressions will make catch-exception redundant. Therefore, this project won’t be maintained any longer

SUT - System Under Test

We will test exceptions thrown by the below 2 classes.

The first one:

class DummyService { public void someMethod () { throw new RuntimeException ( "Runtime exception occurred" ); } public void someOtherMethod ( boolean b ) { throw new RuntimeException ( "Runtime exception occurred" , new IllegalStateException ( "Illegal state" )); } }

And the second:

class DummyService2 { public DummyService2 () throws Exception { throw new Exception ( "Constructor exception occurred" ); } public DummyService2 ( boolean dummyParam ) throws Exception { throw new Exception ( "Constructor exception occurred" ); } }

Desired Syntax

My goal was to achieve syntax close to the one I had with catch-exception library:

package com . github . kolorobot . exceptions . java8 ; import org . junit . Test ; import static com . github . kolorobot . exceptions . java8 . ThrowableAssertion . assertThrown ; public class Java8ExceptionsTest { @Test public void verifiesTypeAndMessage () { assertThrown ( new DummyService ():: someMethod ) // method reference // assertions . isInstanceOf ( RuntimeException . class ) . hasMessage ( "Runtime exception occurred" ) . hasNoCause (); } @Test public void verifiesCauseType () { // lambda expression assertThrown (() -> new DummyService (). someOtherMethod ( true )) // assertions . isInstanceOf ( RuntimeException . class ) . hasMessage ( "Runtime exception occurred" ) . hasCauseInstanceOf ( IllegalStateException . class ); } @Test public void verifiesCheckedExceptionThrownByDefaultConstructor () { // constructor reference assertThrown ( DummyService2 :: new ) // assertions . isInstanceOf ( Exception . class ) . hasMessage ( "Constructor exception occurred" ); } @Test public void verifiesCheckedExceptionThrownConstructor () { // lambda expression assertThrown (() -> new DummyService2 ( true )) // assertions . isInstanceOf ( Exception . class ) . hasMessage ( "Constructor exception occurred" ); } @Test ( expected = ExceptionNotThrownAssertionError . class ) // making test pass public void failsWhenNoExceptionIsThrown () { // expected exception not thrown assertThrown (() -> System . out . println ()); } }

Note: The advantage over catch-exception is that we will be able to test constructors that throw exceptions.

Creating the ‘library’

Syntatic sugar

assertThrown is a static factory method creating a new instance of ThrowableAssertion with a reference to caught exception.

package com . github . kolorobot . exceptions . java8 ; public class ThrowableAssertion { public static ThrowableAssertion assertThrown ( ExceptionThrower exceptionThrower ) { try { exceptionThrower . throwException (); } catch ( Throwable caught ) { return new ThrowableAssertion ( caught ); } throw new ExceptionNotThrownAssertionError (); } // other methods omitted for now }

The ExceptionThrower is a @FunctionalInterface which instances can be created with lambda expressions, method references, or constructor references. assertThrown accepting ExceptionThrower will expect and be ready to handle an exception.

@FunctionalInterface public interface ExceptionThrower { void throwException () throws Throwable ; }

Assertions

To finish up, we need to create some assertions so we can verify our expactions in test code regarding teste exceptions. In fact, ThrowableAssertion is a kind of custom assertion providing us a way to fluently verify the caught exception. In the below code I used Hamcrest matchers to create assertions. The full source of ThrowableAssertion class:

package com . github . kolorobot . exceptions . java8 ; import org . hamcrest . Matchers ; import org . junit . Assert ; public class ThrowableAssertion { public static ThrowableAssertion assertThrown ( ExceptionThrower exceptionThrower ) { try { exceptionThrower . throwException (); } catch ( Throwable caught ) { return new ThrowableAssertion ( caught ); } throw new ExceptionNotThrownAssertionError (); } private final Throwable caught ; public ThrowableAssertion ( Throwable caught ) { this . caught = caught ; } public ThrowableAssertion isInstanceOf ( Class <? extends Throwable > exceptionClass ) { Assert . assertThat ( caught , Matchers . isA (( Class < Throwable >) exceptionClass )); return this ; } public ThrowableAssertion hasMessage ( String expectedMessage ) { Assert . assertThat ( caught . getMessage (), Matchers . equalTo ( expectedMessage )); return this ; } public ThrowableAssertion hasNoCause () { Assert . assertThat ( caught . getCause (), Matchers . nullValue ()); return this ; } public ThrowableAssertion hasCauseInstanceOf ( Class <? extends Throwable > exceptionClass ) { Assert . assertThat ( caught . getCause (), Matchers . notNullValue ()); Assert . assertThat ( caught . getCause (), Matchers . isA (( Class < Throwable >) exceptionClass )); return this ; } }

AssertJ Implementation

In case you use AssertJ library, you can easily create AssertJ version of ThrowableAssertion utilizing org.assertj.core.api.ThrowableAssert that provides many useful assertions out-of-the-box. The implementation of that class is even simpler than with Hamcrest presented above.

package com . github . kolorobot . exceptions . java8 ; import org . assertj . core . api . Assertions ; import org . assertj . core . api . ThrowableAssert ; public class AssertJThrowableAssert { public static ThrowableAssert assertThrown ( ExceptionThrower exceptionThrower ) { try { exceptionThrower . throwException (); } catch ( Throwable throwable ) { return Assertions . assertThat ( throwable ); } throw new ExceptionNotThrownAssertionError (); } }

An example test with AssertJ :

public class AssertJJava8ExceptionsTest { @Test public void verifiesTypeAndMessage () { assertThrown ( new DummyService ():: someMethod ) . isInstanceOf ( RuntimeException . class ) . hasMessage ( "Runtime exception occurred" ) . hasMessageStartingWith ( "Runtime" ) . hasMessageEndingWith ( "occurred" ) . hasMessageContaining ( "exception" ) . hasNoCause (); } }

Frank Appel in his Clean JUnit Throwable-Tests with Java 8 Lambdas post suggests to distinguish act and assert phases of the test for the clear visual separation (improving readability). The idea is to return the Throwable (instead of ThrowableAssert ) in an act phase of the test and then pass it for assertion in the assert phase, so the test will look like the below:

@Test public void aaaStyle () { // arrange DummyService dummyService = new DummyService (); // act Throwable throwable = ThrowableCaptor . captureThrowable ( dummyService :: someMethod ); // assert assertThat ( throwable ) . isNotNull () . hasMessage ( "Runtime exception occurred" ); }

Where ThrowableCaptor is defined as follows:

public class ThrowableCaptor { public static Throwable captureThrowable ( ExceptionThrower exceptionThrower ) { try { exceptionThrower . throwException (); // not exception was thrown return null ; } catch ( Throwable caught ) { return caught ; } } }

Summary

With just couple of lines of code, we built quite cool code helping us in testing exceptions in JUnit without any additional library. And this was just a start. Harness the power of Java 8 and lambda expressions!

References

If you like my articles, please subscribe to http://blog.codeleak.pl here: http://feeds.feedburner.com/codeleak or follow me on twitter: https://twitter.com/kolorobot. I usually blog and tweet about Java and Spring.