Articles | Callibrity IT Consulting | Custom Software Development

JAX-RS Integration Testing with Apache CXF

Written by James Carman | 7/14/20

The Jakarta RESTful Web Services (formerly Java API for RESTful Web Services) specification provides a robust framework for writing RESTful (and not-so-RESTful) web services in Java. The JAX-RS API allows for Resource (the service itself) and Provider (enrichers providing cross-cutting concerns) implementations to be written in a very modular, testable way.

There are times, however, when the API requires a little “magic” that only works well when the code is running within the context of a real JAX-RS implementation. The Apache CXF project provides a JAX-RS implementation module which we can use to test our JAX-RS code in its native environment. First, we need to set up our local development environment.

Getting Started

An example project has been created for your convenience. To get started, simply clone the project to your local workstation:

git clone https://github.com/callibrity/jaxrs-test.git

The example project uses Apache Maven and it already contains all dependencies you will need for following along with the code in this article. You should just need to import the project into your IDE and we can get started creating our first JAX-RS resource.

A Simple Resource

Although not required, it’s best to define your JAX-RS API using an interface:

 
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!