src/main/java/jaxrs/HelloResource.java
This interface will serve as the “contract” to our clients and they can even use it to generate client proxy objects (of the interface’s type) at runtime using the REST Client for MicroProfile . For now, we’ll just implement it:
src/main/java/jaxrs/DefaultHelloResource.java
A Simple Server
We will now use Apache CXF to start up a server which supports the JAX-RS specification:
src/test/java/jaxrs/JaxrsServer.java
Here, we are using the JAXRSServerFactoryBean to configure and start a server listening on port 8080 on our local machine. We have added a LoggingFeature to the server in order to log each request/response, which can be very useful when debugging our integration tests. The HelloResource interface is what actually defines the resource, so we must register it as a resource class and register a resource provider for it, which returns an instance of our DefaultHelloResource class.
Since our HelloResource listens for requests on the /hello path, we can verify that our server is working by visiting http://localhost:8080/hello. The response should simply contain “Hello, World!” Verifying our resource in a browser isn’t terribly reusable. Let’s use this code to create an automated test case.
A Simple Test Case
Using JUnit 5 , let’s write a test case to verify that our resource is responding according to plan:
src/test/java/jaxrs/DefaultHelloResourceTest.java
Here, we have copy and pasted the server initialization logic into a @BeforeEach method, so that the resource will be available for each test case we write. Accordingly, we have created a @AfterEach method to make sure we destroy the server after each test case. We are using CXF’s TestUtil class to find a free port we can use and storing the resulting address in the baseUrl field. In the test case, we are using the JAX-RS Client API to access the resource and asserting that the response is well-formed. Typically, RESTful services use JSON to encode their requests/responses, so we should be too.
Adding a Provider
Our resource is currently providing text/plain responses. The JAX-RS API makes it simple to return more structured responses using JSON. First, let’s define a data structure to embody our responses:
src/main/java/jaxrs/HelloResponse.java
There’s nothing special about this class; it’s just a simple JavaBean containing a single greeting field to contain our greeting. Let’s modify our resource interface to start using it:
src/main/java/jaxrs/HelloResource.java
Here, we’ve modified the return type of the sayHello() method to return a HelloResponse object rather than a String. Also, have changed the media type of the response to application/json. In order to be a little more robust, we’ve also added a @PathParam for the name to be used in the greeting. Let’s now modify our implementation:
src/main/java/jaxrs/DefaultHelloResource.java
Again, there’s nothing really fancy going on here. We are merely instantiating a new HelloResponse and populating its greeting property.
By default, CXF doesn’t know how to serialize objects as JSON. We have to register a Provider to take care of that:
src/test/java/jaxrs/DefaultHelloResourceTest.java
Here (on line 30) we register a JacksonJsonProvider from the Jackson library. This object implements both the MessageBodyReader and MessageBodyWriter provider interfaces from the JAX-RS API in order to serialize objects to/from JSON. Likewise (on line 46) we also register the JacksonJsonProvider with our JAX-RS Client, so that is able to deserialize JSON responses into objects. Our startServer() method is hard-coded at this point to only handle HelloResource and DefaultHelloResource. We can do better.
A JUnit 5 Extension
JUnit 5 provides a very rich extension API which will allow us to abstract our JAX-RS server logic out of our test cases. There are “callback” interfaces which correspond to the @BeforeEach and @AfterEach lifecycle annotations we employed. Let’s use those to write a JUnit 5 extension:
src/test/java/jaxrs/JaxrsServerExtension.java
As you can see, we’ve moved the server startup logic into the beforeEach() method and allowed for any type of resource to be used, not just HelloResources. We have provided a static jaxrsServer() and withProvider() methods as a sort of domain-specific language (DSL) to make the test case code easier to read. We use a Supplier to lazily instantiate the resource implementation as late as possible (we’ll leverage this later). We’ve admired our work long enough, let’s take it for a spin:
src/test/java/jaxrs/DefaultHelloResourceTest.java
Our test case looks much more concise and the extension usage reads very nicely with a nice fluent API, by statically importing the jaxrsServer() method. What happens when our JAX-RS resource leverages dependencies in order to service the requests?
Mocking Dependencies
Ideally, our resource classes should be focusing on the web API itself and adapting “web speak” into “domain speak.” They should delegate to domain services. Let’s create a new Greeter domain service for our resource to use:
src/main/java/jaxrs/Greeter.java
Now, let’s update DefaultHelloResource to use our Greeter domain service:
src/main/java/jaxrs/DefaultHelloResource.java
At this point, we really don’t need to concern ourselves with how the domain service implements its calculateGreeting() method, so we won’t even provide an implementation class. Instead, we’ll rely upon Mockito to generate a mock object:
src/test/java/jaxrs/DefaultHelloResourceTest.java
We are using Mockito to “train” the greeter mock object to return “Howdy, Foo!” when we call it with “Foo” as a parameter. The Supplier we introduced in the previous step allows us to use the greeter mock object after it has been initialized by the MockitoExtension.
By using a mock object, we can strictly control the interactions between our JAX-RS resource implementation and the Greeter domain service. If we want to emulate a failure inside the calculateGreeting() method, we are free to do so. If the Greeter domain service calculates greetings differently depending upon the time of day (good morning, good afternoon, good night, etc), we don’t have to worry about that temporal element during our tests. We can force the Greeter to return an expected result we can use to write our assertions.
Conclusion
By creating the JaxrsServerExtension JUnit 5 extension, we can now author blazing fast JAX-RS integration tests. These tests allow us to test our JAX-RS resource implementations inside a JAX-RS runtime environment so that we can leverage all of the features that JAX-RS has to offer, such as custom payload serializers, exception handling, context injection, etc. Furthermore, by substituting mock objects for the business services leveraged by our JAX-RS resources, we can more tightly control the flow, leading to more predictable, reliable tests. Enjoy writing your new JAX-RS integration tests!