Functional HTTP testing revisited using JUnit 4.7 Interceptors
September 3, 2009
(I originally planned this to be a single article, but because of the scope decided to split it into two parts. Read the first part for the the basics of using Sun’s
HttpServer to conduct functional HTTP testing. Here we revisit our functional test and rewrite it using JUnit 4.7’s new Interceptors feature.)
Recall that I thought my initial solution seemed inelegant. It was verbose, with some start up and shutdown code that would have to be repeated for each test, and which I felt cluttered the actual test code.
It was also tedious, in the sense that the raw
HttpExchange API required us to do quite a few things manually, and unintuitively (such as having to compute and write out the length of our response before the response itself).
In this post, we’ll explore how to use the new Interceptors feature ‘quietly’ released with JUnit 4.7 to write reusable, portable pre and post-test behaviour. I’ll also exhibit a convenient
HttpHandler implementation that simplifies some of the effort required in responding to HTTP requests.
JUnit Interceptors at a glance
Developers familiar to JUnit will know that prior to JUnit 4.x, to perform pre/post test behaviour such as setting up and tearing down test scaffolding or external resources, we had no choice but to override
TestCase#tearDown(). To make this reusable across test classes, we had to extend
TestCase and our actual tests had to extend that custom class.
JUnit 4.x introduced annotation-driven testing using the
@Test annotation, which allowed us to write tests that were simple POJOs. However, this didn’t help when we wanted reusable pre/post test behavior. We still had to write our custom base class, this time with methods annotated with
JUnit 4.7 introduces a new feature called “Interceptors” that aims to bring back to JUnit the ability to to ‘meta-testing’ with a much cleaner and simpler API. See Kent Beck’s1 and David Saff’s2 blog posts for more of the inside scoop.
Writing an Interceptor
A good place to start is to look at the source for
If we need complete control over our interceptor’s behavior, we’d need to implement
MethodRule from scratch.
In our case, we can get by simply by extending
ExternalResource. We setup our HTTP server in the
before() method, and tear it down in our
after() method. We also provide a way to specify the port to listen on in our constructor:
Next, we provide a delegate method to register handlers:
To use our new JUnit interceptor, all we need to do is annotate a public field with the
org.junit.Rule annotation. Note that the field has to be public—this might raise a warning if you use Checkstyle and personally, I wish JUnit would allow private
The JUnit runner does the rest. It’ll transparently call our interceptor’s
before() method, run the
@Test then call our interceptor’s
Armed with this, we can rewrite the rest of our HTTP functional test as:
Simplifying our Handlers
We’ve managed to cut out some of our test scaffolding code. Now let’s simplify our actual handler. For this, I wrote
SimpleHttpHandler, which is an
HttpHandler which provides delegate methods to the actual
HttpExchange. That is, by extending
SimpleHttpHandler we can simply go
getRequestURI() instead of having to go
SimpleHttpHandler uses an internal
ByteArrayOutputStream, exposed via a
PrintWriter in the
getResponse() method. This lets us simply write our response as if, say, to
System.out, and it takes care of computing the total response length later on when we call the
Here’s the same handler above rewritten using
The complete source to
SimpleHttpHandler are provided as part of
junit-rules, a ‘pet’ project I’ve made to explore and showcase JUnit Rules.
Hopefully, I’ll be able to add other, useful rules to
junit-rules. Feel free to download, study and use it. Better yet, if you want to share any useful rules of your own, by all means, please fork or contribute to it!
(Edit 2009/10/16: Changed links/references to
junit-rules, all because I misread Kent Beck’s post—they were renaming it to Rules from Interceptors, and not the other way around!)