Groovy – Testing CXF JAX-RS APIs with Spock

Of the many ways we could create a unit test to exercise a JAX-RS REST API built with CXF and Spring here we work through a pretty easy approach which leverages CXF’s standalone Jetty container and Groovy’s RESTClient.

In Java Quick Start – JAX-RS RESTful services with CXF and TomCat we built a cheap and cheerful REST API using Java. JAX-RS, CXF and Spring. Almost all of the work in a service like this is done in JAX-RS independent classes which we can easily write direct unit tests for, but what about the interface itself?

@Path("/cookingtime")
public interface CookingTimeService {

    @GET
    @Produces({MediaType.APPLICATION_JSON})
    @Path("/{fleshType}/{fleshWeight}")
    public CookingInstruction getCookingTime(
             @PathParam("fleshType") String fleshType,
             @PathParam("fleshWeight") Float fleshWeight)
                      throws AnimalNotFoundException;
}

The interface is annotated with a lot of detail about how our service is going to work, about how the gap between HTTP requests and our Java methods is bridged, all of which is handled by the runtime itself and none of which lends itself particularly well to direct unit tests.

What we’re testing here is really more the configuration of the runtime rather than our code itself and it could reasonably be left to acceptance or integration tests, however when building and modifying APIs like these it’s useful to have unit tests which exercise it directly and confirm immediately whether changes we’re trying do what we expect and more importantly don’t do things we don’t expect.

For a CXF, Spring based JAX-RS like service like our example here, CXF’s lightweight Jetty frontend lends itself quite nicely to embedding a service instance in a unit test, and Spring lends itself quite nicely to tweaking our production configuration to set an endpoint and perhaps provide a lightweight, stubbed service delegate to provide a repeatable, reliable test of how the API behaves with no external dependencies.

Here’s one way to do it using Spock and Groovy for our tests. The full code for this example is on GitHub at https://github.com/devguerrilla/java-quick-starts/tree/master/christmas-turkey-service.

Running a test service

The first thing we’re going to need is to run an instance of our JAX-RS service when our unit test runs. In my project’s src/main/resources folder I have a beans.xml which defines the service like this:-

<jaxrs:server id="cookingTimeServer" address="/">
    <jaxrs:serviceBeans>
        <ref bean="cookingTimeService"/>
    </jaxrs:serviceBeans>
    <jaxrs:extensionMappings>
        <entry key="json" value="application/json" />
    </jaxrs:extensionMappings>
    <jaxrs:providers>
        <ref bean="jsonProvider"/>
        <ref bean="animalNotFoundExceptionProvider"/>
    </jaxrs:providers>
</jaxrs:server>

This is also the configuration I want to use for my unit test server and, as far as possible, I don’t want to have to copy any details into a separate context for testing. The challenge is the address=”/” though, which is fine when deploying into an existing web container but not for running standalone. I could build a CXFServlet container into the Jetty runtime but that’s quite a bit of legwork – if I could just change the address to something like http://localhost:12345/ then CXF will publish it automatically for us when the Spring context starts.

And this actually turns out to be pretty easy with Spring Context’s property-override element. In src/test/resources here’s my test-beans.xml:-

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="testProps"
          class="org.springframework.beans.factory.config.PropertiesFactoryBean">
        <property name="properties">
            <props>
                <prop key="cookingTimeServer.address">
                    http://0.0.0.0:12345/
                </prop>
            </props>
        </property>
    </bean>

    <import resource="classpath:beans.xml"/>

    <context:property-override properties-ref="testProps"/>

</beans>

We’re importing our live spring beans here so we’re using as close to a real configuration as possible, and we’re using property-override to replace the address setting with a specific HTTP base URL. If we were testing a more complex service for which we wanted to stub out the service delegate as well we could use the same approach here.

Job done.

Well, not quite.

Finding a safe port

That fixed port of 12345 has phantom build failures written all over it. If the project is being built twice at the same time on our CI server or if something else happens to have grabbed that port when the test is running we’ll end up with a broken build. Really we need CXF’s Jetty runtime to select a random, available port for us and then tell us what to use.

The first part is easy – simply changing the URL to use port 0 will bring our test service up on a random safe port:-

<bean id="testProps"
      class="org.springframework.beans.factory.config.PropertiesFactoryBean">
    <property name="properties">
        <props>
            <prop key="cookingTimeServer.address">
                http://0.0.0.0:0/
            </prop>
        </props>
    </property>
</bean>

Now we just need to find out what the hell that port is…

Setup and teardown

As mentioned earlier I’m using Spock and Groovy for this test – Spock adds some extra value to the tests by presenting them in a readable, given-when-then spec-by-example format and Groovy gives us both lighter syntax and – as we’ll see shortly – a rather nifty RESTClient to help us along too.

When our test starts it needs to create a Spring context based on our test-beans.xml and then work out what HTTP URL to use to call it. When the test finishes we need to close the context, thereby shutting down our test service. Here’s the code:-

static def ctx = null
static def serviceUrl = null

def setupSpec() {
    ctx = new ClassPathXmlApplicationContext("test-beans.xml")
    serviceUrl = "http://localhost:" +
            ctx.getBean("cookingTimeServer").
                server.destination.engine.connector.localPort +
            "/cookingtime/"
}

def cleanupSpec() {
    ctx.close()
}

The clever, if slightly ugly, bit here is the second line in setupSpec() which interrogates our JAX-RS server instance to ferret out the port it has actually been started on and add it into a serviceUrl property our test methods can call.

This works fine with CXF 3.0 but if you’re stuck on CXF 2.x the server property isn’t public and therefore the above needs a little help from the Reflection APIs:-

static def ctx = null
static def serviceUrl = null

def setupSpec() {
    ctx = new ClassPathXmlApplicationContext("test-beans.xml")
    def server = ctx.getBean("cookingTimeServer")
    def serverProp = server.getClass().getSuperclass().
            getDeclaredField("server")
    serverProp.setAccessible(true)
    serviceUrl = "http://localhost:" + serverProp.get(server).
            destination.engine.connector.localPort + "/cookingtime/"
}

def cleanupSpec() {
    ctx.close()
}

Testing a successful call

A quick refresher at this point – my CookingTime service is invoked through a URL containing both the type of meat and the weight, which will return some JSON to tell me what temperature to cook it at and for how long. My success test case therefore needs to invoke a valid URL and interrogate the returned JSON to verify the results:-

def "Request for a known type of meat is successfully processed"() {
    given: "A type of meat the service knows how to cook"
        def myDinner = "turkey"
      and: "A valid weight in kilograms"
        def weight = 1.8

    when: "We request cooking instructions from the server"
        def response = new RESTClient(serviceUrl).
                get(path : myDinner + "/" + weight)

    then: "the server returns successfully"
        response.status == 200
     and: "the instructions are correct"
        response.data.cookingInstruction.temperatureToSet == 180
        response.data.cookingInstruction.timeToCook == 120
}

Groovy’s RESTClient does all the heavy lifting for us here – we create an instance associated with our root service URL and then execute an HTTP GET against the required sub-path – in this case turkey/1.8. Its response object parses the JSON for us into an object model we can then interrogate to ensure the expected numbers are returned.

RESTClient is actually part of Groovy’s HTTPBuilder module – if you want to use it in your tests you’ll need to add the following to your Maven dependencies:-

<dependency>
    <groupId>org.codehaus.groovy.modules.http-builder</groupId>
    <artifactId>http-builder</artifactId>
    <version>0.7.1</version>
    <scope>test</scope>
</dependency>

Testing an unsuccessful call

There are a couple of error scenarios I’d like to test and ensure they behave as expected. First of all, what happens if we supply only the first parameter (the type of meat) but not an associated weight? Our JAX-RS service doesn’t actually have a mapping for such a URI so the container should generate a 404 for us:-

def "Request without a weight fails with an appropriate error"() {
    given: "A type of meat the service knows how to cook"
        def myDinner = "turkey"

    when: "We request cooking instructions from the server with no weight"
        def response = new RESTClient(serviceUrl).get(path : myDinner)

    then: "the request fails with a 404 (not found error)"
        def error = thrown(HttpResponseException.class)
        error.statusCode == 404
}

RESTClient throws an HttpResponseException in this case which we can assert as thrown and validate contains the correct HTTP status code. This is great for container generated errors where we’re returning no additional details, but not so great if we’re sending back additional error information in the payload. 

Testing an unsuccessful call which returns extra information

If a user calls our service with a type of meat we don’t know how to cook and a valid weight then the URI will be passed through to our code and our exception handling generates a 404 status code with some helpful JSON error text:-

{"message":"Sorry, don't know how to cook poodle"}

In order to verify this behaviour in a test we need to override RESTClient’s error handling so we still get a parsed response back:-

def "Request for an unknown meat fails with a meaningful error"() {
    given: "A type of meat the service does not know how to cook"
        def myDinner = "poodle"
      and: "A valid weight in kilograms"
        def weight = 2.5

    when: "We request cooking instructions from the server"
        def client = new RESTClient(serviceUrl)
        client.handler.failure =
                { resp, data -> resp.setData(data); return resp }
        def response = client.get(path : myDinner + "/" + weight)

    then: "the server indicates the type of meat was not found"
        response.status == 404
     and: "an explanatory message is supplied"
        response.data.message == "Sorry, don't know how to cook poodle"
}

The key to doing this is to set the handler.failure property to a closure which preserves the data from the response.

The full working code for this example is on GitHub at https://github.com/devguerrilla/java-quick-starts/tree/master/christmas-turkey-service.

Leave a Reply

Your email address will not be published. Required fields are marked *