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(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 Hamcrestpresented 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();
    }
}

Update - AAA Style

Frank Appel in his Clean JUnit Throwable-Tests with Java 8 Lambdas post suggests to distinguishact 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.

Popular posts from this blog

Parameterized tests in JavaScript with Jest

macOS: Insert current date shortcut with `Shortcuts.app`