JAX-RS Testing
JAX-RS endpoints can be easily tested using the embedded Jetty or CXF Local Transport.
Embedded Jetty
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http-jetty</artifactId>
<version>3.0.0</version>
</dependency>
import javax.ws.rs.core.Response;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
import org.junit.Test;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.AfterClass;
import custom.MyJaxrsResource;
import custom.Book;
public class JAXRSTest extends Assert {
private final static String ENDPOINT_ADDRESS = "http://localhost:8080/rest";
private final static String WADL_ADDRESS = ENDPOINT_ADDRESS + "?_wadl";
private static Server server;
@BeforeClass
public static void initialize() throws Exception {
startServer();
waitForWADL();
}
private static void startServer() throws Exception {
JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
sf.setResourceClasses(MyJaxrsResource.class);
List<Object> providers = new ArrayList<Object>();
// add custom providers if any
sf.setProviders(providers);
sf.setResourceProvider(MyJaxrsResource.class.class,
new SingletonResourceProvider(new MyJaxrsResource(), true));
sf.setAddress(ENDPOINT_ADDRESS);
server = sf.create();
}
// Optional step - may be needed to ensure that by the time individual
// tests start running the endpoint has been fully initialized
private static void waitForWADL() throws Exception {
WebClient client = WebClient.create(WADL_ADDRESS);
// wait for 20 secs or so
for (int i = 0; i < 20; i++) {
Thread.currentThread().sleep(1000);
Response response = client.get();
if (response.getStatus() == 200) {
break;
}
}
// no WADL is available yet - throw an exception or give tests a chance to run anyway
}
@AfterClass
public static void destroy() throws Exception {
server.stop();
server.destroy();
}
@Test
public void testGetBookWithWebClient() {
WebClient client = WebClient.create(ENDPOINT_ADDRESS);
client.accept("text/xml");
client.path("somepath");
Book book = client.get(Book.class);
assertEquals(123L, book.getId());
}
@Test
public void testGetBookWithProxy() {
MyJaxrsResource client = JAXRSClientFactory.create(ENDPOINT_ADDRESS, MyJaxrsResource.class);
Book book = client.getBook();
assertEquals(123L, book.getId());
}
}
It is quite easy to setup a server and start testing it. The advantage of using the embedded Jetty is that a complete end-to-end round-trip can be exercised, thus stressing all the CXF runtime which comes at the cost of some added complexity to do with setting up the server.
Local Transport
Starting from CXF 2.6.2 it is possible to use CXF Local Transport for testing the JAX-RS endpoints and clients.
This avoids the need to start an embedded servlet container and the tests will run faster.
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-local</artifactId>
<version>3.0.0</version>
</dependency>
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
import org.apache.cxf.transport.local.LocalConduit;
import org.junit.Test;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.AfterClass;
import custom.MyJaxrsResource;
import custom.Book;
public class JAXRSTest extends Assert {
private final static String ENDPOINT_ADDRESS = "local://books";
private static Server server;
@BeforeClass
public static void initialize() throws Exception {
startServer();
}
private static void startServer() throws Exception {
JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
sf.setResourceClasses(MyJaxrsResource.class);
List<Object> providers = new ArrayList<Object>();
// add custom providers if any
sf.setProviders(providers);
sf.setResourceProvider(MyJaxrsResource.class.class,
new SingletonResourceProvider(new MyJaxrsResource(), true));
sf.setAddress(ENDPOINT_ADDRESS);
server = sf.create();
}
@AfterClass
public static void destroy() throws Exception {
server.stop();
server.destroy();
}
@Test
public void testGetBookWithWebClientDirectDispatch() {
WebClient client = WebClient.create(ENDPOINT_ADDRESS);
WebClient.getConfig(client).getRequestContext().put(LocalConduit.DIRECT_DISPATCH, Boolean.TRUE);
client.accept("text/xml");
client.path("somepath");
Book book = client.get(Book.class);
assertEquals(123L, book.getId());
}
@Test
public void testGetBookViaPipe() {
WebClient client = WebClient.create(ENDPOINT_ADDRESS);
client.accept("text/xml");
client.path("somepath");
Book book = client.get(Book.class);
assertEquals(123L, book.getId());
}
@Test
public void testAddBookWithProxyDirectDispatch() {
MyJaxrsResource client = JAXRSClientFactory.create(ENDPOINT_ADDRESS, MyJaxrsResource.class);
WebClient.getConfig(client).getRequestContext().put(LocalConduit.DIRECT_DISPATCH, Boolean.TRUE);
Book book = client.echoBook(new Book(123));
assertEquals(123L, book.getId());
}
}
Note that setting a LocalConduit.DIRECT_DISPATCH property to 'true' ensures that the invocation goes immediately into the service chain after the client out chain has completed.
If this property is not set then CXF LocalConduit sets up a pipe which is initiated via an initial write on the client side.
In the above code example Local transport is activated by using a URI "local:" scheme, for example, "local://books".
Alternatively, the address can be set as a relative value such as "/books", with the server and client transportId attribute set to
"http://cxf.apache.org/transports/local". In this case, when creating the clients, use JAXRSClientFactoryBean:
private static void startServer() throws Exception {
JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
sf.setTransportId(LocalTransportFactory.TRANSPORT_ID);
sf.setAddress("/books");
sf.setResourceClasses(MyJaxrsResource.class);
server = sf.create();
}
@Test
public void testProxyPipedDispatchGet() throws Exception {
JAXRSClientFactoryBean bean = new JAXRSClientFactoryBean();
bean.setTransportId(LocalTransportFactory.TRANSPORT_ID);
bean.setAddress("/books");
bean.setServiceClass(MyJaxrsResource.class);
MyJaxrsResource localProxy = bean.create(MyJaxrsResource.class);
Book book = localProxy.getBook("123");
assertEquals(123L, book.getId());
}
Mocking HTTP contexts
If you test a code which depends on the injected HTTP contexts such as HttpServletRequest then these contexts will have to be mocked.
For example:
JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
sf.setInvoker(new Invoker() {
Invoker jarsInvoker = new JAXRSInvoker();
@Override
public Object invoke(Exchange exchange, Object o) {
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getRemoteHost()).thenReturn("host");
exchange.getInMessage().put(AbstractHTTPDestination.HTTP_REQUEST, request);
return jarsInvoker.invoke(exchange, o);
}
});