JUnit: testing exception with Java 8 and Lambda Expressions
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() { 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() { assertThrown(() -> new DummyService().someOtherMethod(true)) // lambda expression // assertions .isInstanceOf(RuntimeException.class) .hasMessage("Runtime exception occurred") .hasCauseInstanceOf(IllegalStateException.class); } @Test public void verifiesCheckedExceptionThrownByDefaultConstructor() { assertThrown(DummyService2::new) // constructor reference // assertions .isInstanceOf(Exception.class) .hasMessage("Constructor exception occurred"); } @Test public void verifiesCheckedExceptionThrownConstructor() { assertThrown(() -> new DummyService2(true)) // lambda expression // 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(); } }
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!
Resources
- Source code for this article is available on GitHub (have a look at
com.github.kolorobot.exceptions.java8
package) - Some other articles of mine about testing exceptions in JUnit. Please have a look:
Reference: | JUnit: testing exception with Java 8 and Lambda Expressions from our JCG partner Rafal Borowiec at the Codeleak.pl blog. |