Core Java

Clean JUnit Throwable-Tests with Java 8 Lambdas

Recently I was involved in a short online discussion on twitter and google+ which concerned the question why the arrival of Java 8 Lambda expressions makes the catch-exception library1 obsolete. This was triggered by a brief announcement that the library won’t be longer maintained as lambdas will make it redundant.

The answer I came up with at that time has a lot in common with the one presented by Rafał Borowiec in his well written post JUNIT: TESTING EXCEPTION WITH JAVA 8 AND LAMBDA EXPRESSIONS. Giving both approaches a second thought however, I believe one could do even a bit better with respect to clean code.

So this post is a trackback on that topic which shares my latest considerations and explains concisely a slightly refined solution. This way I hopefully will find out about the weak points soon…

Motivation

While writing tests I always strive to end up with a clear visual separation of the arrange/act/assert2 phases in a test method (and I am under the impression that it is getting more and more popular to emphasize those phases optically by using empty lines as separator).

Now it seems to me that the catch-exception solutions mentioned above mix the act and assert phases more or less together. This is because both assert that a Throwable has been thrown while still being in the act phase. But an assertion belongs apparently to the assert phase.

Fortunately this problem can be solved easily.

Refinement

Let’s have look at a simple example to explain how the refined approach might look like. I start with a class that provides a method throwing an IllegalStateException for demonstration purpose:

public class Foo {

  static final String ERR_MESSAGE = "bad";

  public void doIt() throws IllegalStateException {
    throw new IllegalStateException(ERR_MESSAGE);
  }
}

The next snippet introduces a little helper that is responsible for capturing a Throwable thrown during the act phase of a JUnit test. Note that it does not assert anything by itself. It simply returns the captured Throwable if any or null otherwise.

public class ThrowableCaptor {

  public interface Actor {
    void act() throws Throwable;
  }

  public static Throwable captureThrowable( Actor actor ) {
    Throwable result = null;
    try {
      actor.act();
    } catch( Throwable throwable ) {
      result = throwable;
    }
    return result;
  }
}

To highlight that the ThrowableCaptor is used to deal with the act phase of a JUnit Test the captorThrowable method takes a parameter of a type Actor – which admittedly might overdue the metaphor a bit…

Anyway, with that utility in place, AssertJ for clean matcher expressions, static imports and Java 8 lambdas at hand, an exception test might look like this:

public class FooTest {

  @Test
  public void testException() {
    // arrange
    Foo foo = new Foo();
    
    // act
    Throwable actual = captureThrowable( foo::doIt );
    
    // assert
    assertThat( actual )
      .isInstanceOf( IllegalStateException.class )
      .hasMessage( Foo.ERR_MESSAGE );
  }
}

For clarification I have inserted comments to depict the clear separation of the three phases in the test method. In case that no exception is thrown the assert block would quit this with an assertion error noting that ‘Expecting actual not to be null’3.

Conclusion

By moving the Throwable existence check from the act to the assert phase, the catch-exception approach based on Java8 lambda expressions allows to write such tests in a pretty clean way – at least from my current point of view.

So what do you think? Am I missing something?
 

  1. I order to make exception testing cleaner, the catch-exception library catches exceptions in a single line of code and makes them available for further analysis
  2. See Practical Unit Testing, Chapter 3.9. Phases of a Unit Test, Tomek Kaczanowski 2013, often also denoted as build-operate-check pattern, Clean Code, Chapter 9. Unit Tests, Robert C. Martin 2009
  3. The Assertion#isNotNull check is implicitly called by Assertion#isInstanceOf, but it can be called also explicitly of course
Reference: Clean JUnit Throwable-Tests with Java 8 Lambdas from our JCG partner Frank Appel at the Code Affine blog.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button