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?
- 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
- 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
- The
Assertion#isNotNull
check is implicitly called byAssertion#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. |