« Back to home

Jersey Tests with Embedded Jetty and Spring

Posted on
One of the bittersweet things about using Java is that there's a library for everything.   The good is that if you need to do something, there's a library.  The bad is that the documentation is typically terrible, and it can take hours (or days!) to figure out how the hell to make something work.  Today's lesson in this is testing Jersey services when you're deploying on Embedded Jetty and Spring.

The problem of using this combo is that we don't use web.xml for configuring things, it's all done with Spring.  And I want to test with InMemoryTestContainer.  So how is it done?

Let's say you have this service:


1    package com.trimbo.web.api; 
2
3
import freemarker.template.Configuration;
4
import freemarker.template.DefaultObjectWrapper;
5
import freemarker.template.Template;
6
import freemarker.template.TemplateException;
7
import org.springframework.context.annotation.Scope;
8
import org.springframework.stereotype.Component;
9
10
import javax.ws.rs.GET;
11
import javax.ws.rs.Path;
12
import javax.ws.rs.Produces;
13
import javax.ws.rs.core.MediaType;
14
import java.io.IOException;
15
import java.io.StringWriter;
16
17 @Path(
"/jobs")
18 @Component
19 @Scope(
"prototype")
20
public class TestService extends JerseySpringServlet {
21 @GET
22 @Path(
"/")
23 @Produces(MediaType.APPLICATION_JSON)
24
public String index() {
25
return("{ 'a': 'test' }");
26 }
27 }


Then you have this configuration in Spring






1    xml version="1.0" encoding="UTF-8"?> 
2
<beans xmlns="http://www.springframework.org/schema/beans"
3
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
5
<beans xmlns="http://www.springframework.org/schema/beans"
6
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
7
xmlns:context="http://www.springframework.org/schema/context"
8
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">
9
<context:component-scan base-package="com.trimbo.web.api" />
10
<bean id="JettyServer" class="org.mortbay.jetty.Server"
11
init-method="start"
12
destroy-method="stop">
13
<property name="connectors">
14
<list>
15
<bean id="connector" class="org.mortbay.jetty.nio.SelectChannelConnector">
16
<property name="port" value="8050"/>
17
<property name="maxIdleTime" value="30000"/>
18
<property name="acceptors" value="10"/>
19
bean>
20
list>
21
property>
22
<property name="handlers">
23
<list>
24
<bean class="org.mortbay.jetty.servlet.Context">
25
<property name="contextPath" value="/"/>
26
<property name="servletHandler">
27
<bean class="org.mortbay.jetty.servlet.ServletHandler">
28
<property name="servlets">
29
<list>
30
<bean class="org.mortbay.jetty.servlet.ServletHolder">
31
<property name="servlet">
32
<bean class="com.trimbo.web.api.JerseySpringServlet" />
33
property>
34
<property name="name" value="jersey_api"/>
35
bean>
36
list>
37
property>
38
<property name="servletMappings">
39
<list>
40
<bean class="org.mortbay.jetty.servlet.ServletMapping">
41
<property name="pathSpec" value="/api/*"/>
42
<property name="servletName" value="jersey_api"/>
43
bean>
44
list>
45
property>
46
bean>
47
property>
48
bean>
49
<bean class="org.mortbay.jetty.handler.DefaultHandler" />
50
<bean class="org.mortbay.jetty.handler.RequestLogHandler" />
51
list>
52
property>
53
bean>
54
beans>
55
beans>


And here is your glue class for that JerseySpringServlet, which injects the App context.






1    package com.trimbo.web.api; 
2
3
import com.sun.jersey.spi.spring.container.servlet.SpringServlet;
4
import org.springframework.beans.factory.annotation.Autowired;
5
import org.springframework.context.ApplicationContext;
6
import org.springframework.context.ConfigurableApplicationContext;
7
8
public class JerseySpringServlet extends SpringServlet
9 {
10 @Autowired
11
private ApplicationContext applicationContext;
12
13 @Override
14
protected ConfigurableApplicationContext getContext()
15 {
16
return (ConfigurableApplicationContext) this.applicationContext;
17 }
18 }
Then this would be your test class that uses InMemoryTestContainer
1    package com.trimbo.web.api; 
2
3
import com.sun.jersey.api.client.ClientResponse;
4
import com.sun.jersey.api.client.WebResource;
5
import com.sun.jersey.test.framework.AppDescriptor;
6
import com.sun.jersey.test.framework.JerseyTest;
7
import com.sun.jersey.test.framework.WebAppDescriptor;
8
import com.sun.jersey.test.framework.spi.container.TestContainerFactory;
9
import com.sun.jersey.test.framework.spi.container.inmemory.InMemoryTestContainerFactory;
10
import org.junit.Test;
11
import org.springframework.web.context.ContextLoaderListener;
12
13
import static junit.framework.Assert.assertEquals;
14
15
public class TestServiceTest extends JerseyTest {
16 @Override
17
protected TestContainerFactory getTestContainerFactory() {
18
return new InMemoryTestContainerFactory();
19 }
20
21
22 @Override
23
protected AppDescriptor configure() {
24
return
25
new WebAppDescriptor.Builder("com.trimbo.web.api")
26 .contextPath(
"/")
27 .contextParam(
"contextConfigLocation", "classpath:service-spring-config.xml")
28 .contextListenerClass(ContextLoaderListener.
class)
29 .build();
30 }
31
32 @Test
33
public void testIndex() throws Exception {
34 WebResource resource = resource().path(
"/jobs");
35 ClientResponse resp = resource.get(ClientResponse.
class);
36 assertEquals(
200, resp.getStatus());
37 }
38 }
This basically configures an App Descriptor with the prefix of our package, the "/" path in web, and uses our spring config as the context.  Note that the TestContainerFactory is our InMemoryTestContainerFactory.
Basically I'm writing this down for myself.  Hopefully it helps someone else too.