Recipe: Unit testing Apache CXF RESTful services

Recently, decided to use Apache CXF to expose a service with a RESTful API. Part of the reason for choosing REST had more to do with the fact that the client is going to be a mobile client. These days, though mobile devices stacks have come a long way and provide SOAP clients, it still seems prudent to not depend on a whole slew of technologies where plain ‘ole HTTP and JSON might do the trick.
As I started exploring CXF, I liked the JAX-RS implementation and decided to go ahead with it – however, almost immediately, hit a snag when I went on to write test cases. Apache CXF documentation is not quite there and things do require some investigation – at least initially till you get a hang of the framework. As it took time to figure out the solution, it makes sense to share it on blogosphere. Here’s how to go about writing unit tests:

Firstly, the service and the service implementation:

package com.aditi.blackberry.web;

import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;

@Path("/chat")
@Produces("application/json")
public interface ChatWebService {

 @POST
 @Path("connect")
 public Response connect(@FormParam("user")String username, @FormParam("pass")String password);
}

The service implementation:

package com.aditi.blackberry.web;

import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

@Produces("application/json")
public class ChatWebServiceImpl implements ChatWebService {

 public Response connect(String username, String password) {
 if(username ==null || "".equals(username) ||
 password ==null || "".equals(password)) {
 return Response.status(Status.BAD_REQUEST).build();
 }
 String[] response = {username, password};
 return Response.ok(response).build();
 }
}

The corresponding spring context xml (applicationContext.xml) is:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:jaxrs="http://cxf.apache.org/jaxrs" xmlns:cxf="http://cxf.apache.org/core"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
       http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
       http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd">

	<import resource="classpath:META-INF/cxf/cxf.xml" />
	<import resource="classpath:META-INF/cxf/cxf-extension-jaxrs-binding.xml" />

	<bean id="logInbound" class="org.apache.cxf.interceptor.LoggingInInterceptor" />
	<bean id="logOutbound" class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
	<!--
     logging
     -->
	<cxf:bus>
		<cxf:inInterceptors>
			<ref bean="logInbound" />
		</cxf:inInterceptors>
		<cxf:outInterceptors>
			<ref bean="logOutbound" />
		</cxf:outInterceptors>
		<cxf:inFaultInterceptors>
			<ref bean="logOutbound" />
		</cxf:inFaultInterceptors>
	</cxf:bus>
	<jaxrs:server id="jaxrsservice" address="${server.address}" >
		<jaxrs:serviceBeans>
			<ref bean="chatwebservice" />
		</jaxrs:serviceBeans>
		<jaxrs:providers>
			<ref bean="flexjsonprovider"/>
		</jaxrs:providers>
	</jaxrs:server>
	<bean id="flexjsonprovider" class="com.aditi.blackberry.web.FlexJsonMessageBodyWriter" />
	<bean id="chatwebservice" class="com.aditi.blackberry.web.ChatWebServiceImpl" />
</beans>

A few things to note here – logging is turned on using interceptors and the jaxrs server is defined. I’m also using flexJson to convert arbitrary objects to json – so a MessageBodyWriter bean is also injected into the jaxrs server node. The most important thing is that we havent included either the cxf-servlet.xml config for the cxf-extension-http-jetty.xml. Essentially, what we want to do is for the actual build, include cxf-servlet.xml and for the test runs, run the service on the bundled jetty server.

So, go ahead and define a applicationContext-web.xml:

        <bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:/build.properties" />
	</bean>
	<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
	<import resource="classpath:/applicationContext.xml" />

This is the context xml that we’ll provide to the ContextLoaderListener in our web.xml.

For the test cases, define applicationContext-test.xml – this is the context xml which we’ll load from the test cases.

	<bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:/test.properties" />
	</bean>
	<import resource="classpath:META-INF/cxf/cxf-extension-http-jetty.xml" />
	<import resource="classpath:/applicationContext.xml" />
	<jaxrs:client id="chatclient" address="${server.address}" serviceClass="com.aditi.blackberry.web.ChatWebService">
		<jaxrs:headers>
			<entry key="Accept" value="application/json"></entry>
		</jaxrs:headers>
	</jaxrs:client>

As you see, we also define a jaxrs:client for the test context xml.

There’s one final issue to address – which is that we would ideally like the urls we use to access the service to be the same. The spring jaxrs:server binding takes an address attribute which defines the url the service is hosted on. For deployment onto an external container, this takes the form of “/myservice” – a path element relative to the context location. For the internal jetty hosted service, it takes the full http path (http://localhost:port/my/path/to/service). The easiest way is to have this set using a property reference in spring and have the applicationContext-web.xml and applicationContext-test.xml load different property files as shown in above.

For completeness, here’s the web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

	<display-name>CXF REST Example</display-name>
	<description>CXF REST Example</description>

	<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/applicationContext-web.xml</param-value>
	</context-param>
	<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<servlet>
		<servlet-name>CXFServlet</servlet-name>
		<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>CXFServlet</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>

</web-app>

And finally, here’s a junit test case:

base class:

package com.aditi.blackberry.web;

import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/applicationContext-test.xml" })
public abstract class AbstractApiTest {

	@Autowired
	@Qualifier("chatclient")
	protected ChatWebService proxy;
}

A test case for the connect API:

package com.aditi.blackberry.web;

import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import org.junit.Assert;
import org.junit.Test;

public class ConnectApiTest extends AbstractApiTest{
	@Test
	public void testConnect() {
		Response resp = proxy.connect("raghu", "password");
		Assert.assertTrue(resp.getStatus() == 200);
		System.out.println(resp.getEntity().toString());
	}
}
Advertisements

22 thoughts on “Recipe: Unit testing Apache CXF RESTful services

  1. Hi Raghu great tutorial. Best so for on the subject. Can you post the full code? I seem to be having problems with the providers. I can seem to make it work.

    Thanks

    Boni

  2. @Sanjay, @David and others

    Really sorry about the source – I’d written this up quite some time ago and have changed machines thrice since then and dont have the source 😦 😦 on my current machine.

    I’ll look around some more to see if I can find it – and if so will put it up on github or something.

  3. Pingback: Unit testing Apache CXF RESTful services – code available « Nifty tidbits
  4. Pingback: Unit testing Apache CXF RESTful services – code available « Nifty tidbits
  5. For some reason the embedded Jetty starts up and just waits at the port. The tests don’t detect that jetty is ready somehow and never invoke any requests. I end up getting a timeout. Any idea why this is happening?

  6. I was wondering, do you know of anyway to attach debugging to the embedded Jetty instance running for these tests, or maybe if the Jetty service logs somewhere? I’m having trouble debugging tests that I’ve set up like this. My test expects a 201 for example, but gets a 500 with no response, I have no insight into what happened on the server.

    Any tips would be GREAT!

    • Don’t you see the logs in the eclipse window (if in the IDE) or on the console when running with mvn?

      You could also debug the test from eclipse which will allow you to step through code on the Eclipse Debug Perspective…

      • No I wasn’t seeing anything in the IDE console from the server side (embedded Jetty). I did find something that i added to the VM args that now is showing the logs now from Jetty so I do see the logs now. But the debugger will not attach to the embedded Jetty instance because I believe it’s running in a different VM, not in the same one the IDE is running in. So to debug you’d have to either stop the forking of the Jetty instance into a different VM (not sure how to do this) or attach as a remote debugging process, which I don’t know how to do.

      • If anyone is interested, these are the VM arguments for enabling the debug logs from jetty:

        -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog -D{classref}.LEVEL=DEBUG

  7. Hi,

    I have just setup a similar environment for testing REST service but have encounter an issue :
    “Connection refused connect”

    It seems to me that Jetty is not started anywhere (as per the logs of Spring).

    So I am confused.
    Is this recipe really supposed to work standalone and to start a Jetty server itself ?
    Or do I need to instantiate a Jetty server myself ?

    Is this line supposed to launch a Jetty server ?

    My purpose is to be able to launch a pseudo-integration test, standalone, without having to deploy my application on a real application server.

    Do you have any hint/resource on it ?
    Any help would be appreciated.

    Thanks

    • @Raphael Jolivet

      Yes – it should start embedded Jetty. You shouldn’t need to do anything else other than mvn test. Looks like the line you posted is missing/got stripped off.

      If you don’t see Jetty starting up, do you have the right dependencies on the test project on the CXF jetty runtime?

      org.apache.cxf
      cxf-rt-transports-http-jetty
      ${cxf.version}

      Have you cloned the repo and tried running that? It does exactly what you’re looking for…

  8. How much time did it require you to compose “Recipe:
    Unit testing Apache CXF RESTful services | Nifty tidbits”?
    It has quite a bit of decent material. Thanks -Angela

    • @Angela – don’t remember – I did it when I started poking into CXF and took me a bit of time – probably a couple of days… Promptly wrote a post about it and forgot everything… but there were so many requests for code that I finally dug out my old svn repo where I had it and just pushed it up to github.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s