Interceptors in JUnit

<<<update>>> We have changed the name of the abstraction to “Rule” from “Interceptor”. The code and documentation read much better. <<<>>>

It must have been the little one-week-old on his lap. Saff and I had a remarkable programming session Tuesday night. Maybe once every five years unsuspectedly powerful abstractions drop out of a program with no apparent effort. As it turns out we’ve been preparing for this particular magical moment for a while, but it was nice to have it happen finally. And I really think the baby did the trick.

Meta-testing

JUnit is good for expressing tests as little bits of logic. In JUnit 3 you could also manipulate the test running process itself in various ways. One of the prices of the simplicity of JUnit 4 was the loss of this “meta-testing”. It doesn’t affect simple tests, but for more powerful tests it can be constraining. The object framework style of JUnit 3 lent itself to extension by default. The DSL style of JUnit 4 doesn’t. Last night we brought back meta-testing, but much cleaner and simpler than before.

For example, suppose you want to write to a log every time a test fails. As of late last night, when you annotate a field with @Interceptor, the object in that field is notified before the test is run. Thus, you can write:

@Interceptor
public StatementInterceptor logger=
new LoggingInterceptor();

With the @Interceptor declaration, logger is called before the test is run. For the moment interceptors aren’t part of the standard test runner, so you need to run tests containing interceptors with a special runner, like this (we expect to make interceptors part of the default behavior in the 4.7 release):

@RunWith(Interceptors.class)
public class MyLoggingTest {
@Interceptor
public StatementInterceptor logger=
new LoggingInterceptor();

}

We created a simple general interceptor, TestWatchman, that contains hook methods called when a test succeeds or fails. Extending TestWatchman lets you log errors:

public class LoggingInterceptor
extends TestWatchman {
public void failed(
Throwable e,
FrameworkMethod method) {
log(method.toString());
}
}

So, the simple way to introduce an interceptor is to declare a field as a StatementInterceptor, annotate it with @Interceptor, and initialize it with an instance of your own subclass of TestWatchman in which you override succeeded() or failed().

Writing Interceptors

That’s just the beginning of what you can do with interceptors. You can also define your own interceptors from scratch. Two More Implementation Patterns describes the implementation of test runners in some detail. Here is a summary. Each test is run by a chain of Statement objects. Each Statement takes care of one aspect of test running, like running @Before methods or causing a test to fail if a timeout is exceeded. The whole “block” of “statements” that runs an ordinary test looks like this:

RunAfters -> RunBefores -> InvokeMethod

Interceptors are inserted into this block. For the LoggingInterceptor example above, the block looks like this:

RunAfters -> LoggingInterceptor ->
RunBefores -> InvokeMethod

(Actually, now that I look at it, RunAfters and RunBefores should only appear if there are actually @Before/@After methods, but that’s an optimization for another day.)

You can’t control where your interceptors will be inserted into the block. You just know they’ll be in there somewhere. As mentioned above, the object occupying the @Interceptor field must implement StatementInterceptor. StatementInterceptor declares a factory method intercept() that produces a Statement of your own devising given the next Statement in the block and a FrameworkMethod. FrameworkMethod is JUnit’s version of java.lang.reflect.Method, wrapping a Method and providing additional behavior to validate methods for use as tests.

public class MyInterceptor
implements StatementInterceptor {
public Statement intercept(
final Statement next,
final FrameworkMethod method) {
return new Statement() {
public void evaluate() throws Throwable {
// here is where you put the behavior you want
next.evaluate();
// or here
}
}
}
}

Note that you should generally not capture the Throwable generated by the recursive call to evaluate(), although you may want to catch it and rethrow it after processing it in some way.

Conclusion

We have only begun exploring what is possible with interceptors. Many of the extensions people have requested that would have required an entire custom test runner can now be implemented with interceptors. Since interceptors are just fields in the test object, it should also be possible to store information during test running and process that information after the test completes. And I’m sure Saff will have some excellent examples, just as soon as he gets that diaper cleaned up.

If you want to try interceptors, you need to check JUnit out of the master repository on GitHub: git://github.com/KentBeck/junit.git. If there is sufficient interest we will also upload a snapshot to SourceForge.

11 Comments

Joakim OhlroggeApril 30th, 2009 at 7:10 am

Well done! Looks very promising indeed!

Daniel BrolundApril 30th, 2009 at 7:26 am

Looks like a giant leap in a good direction. How does the abstraction carry to suites?

I dream of being able to annotate a listener on the root suite, and then get all the events: before/after suite, test case, test method (fail/success).

Would that be possible?

[...] both of you that read this blog, and haven’t already seen it, there’s some fun stuff brewing in JUnit [...]

[...] and skill). All this goes to reduce the cost of slow and careful design. Last week we added interceptors. This is a change that has been percolating for a couple of years. Taking that long to actually try [...]

[...] the latest snapshot build of JUnit 4.7, we’ve re-implemented expected exceptions using Interceptors. Share and [...]

[...] thoroughly we made sure the new tests worked alongside older tests. For the recent introduction of interceptors, though, we carefully isolated the code that ran tests and made it modular before finding a new way [...]

JUnit 4.7RC-1 Rules « Cato’s PlaceJuly 24th, 2009 at 6:57 am

[...] start with I begun reading Kent Beck’s blog entry about Inteceptors in JUnit which was originally written prior to the renaming of Inteceptors to [...]

A.ClarkAugust 1st, 2009 at 3:07 am

That’s awesome ! I can’t expect to use this new feature

[...] Interceptors in JUnit 2 JUnit 4.7: Interceptors: expected exceptions Posted by Alistair Filed in Uncategorized [...]

NealeSeptember 3rd, 2009 at 5:48 am

I’m wondering if the example you give in the release notes helps solve the downside of @Test(expect=SomeException.class), which can pass if the expected exception is thrown from the wrong place.

Would the following be valid:
@Rule
public ExpectedException thrown= ExpectedException.none();

@Test
public void throwsNullPointerExceptionWithMessage() {
maybeThrowNPE(); // should fail if this does throw NPE..?
thrown.expect(NullPointerException.class);
throw new NullPointerException();
}

[...] 4.7 of JUnit introduced rules, sort-of around aspects that are called before and after the execution of a test. Some of the [...]