I've been developing a way to deal with this, too, and I went a completely
different direction. My goal was to be able to run the JAX-RS resource in
the Jersey container but mock whatever bean(s) that it depends on in order
to write very isolated tests just against the REST calls provided by the
resource. As a trivial example:
@Path("/foo")
class FooJaxRsResource {
@Autowired
private FooService service;
@GET
@Path("/something")
public Foo doWhatever() {
return service.getAFoo();
}
}
interface FooService {
Foo getAFoo();
}
The main problem was how to get a mock instance into the ApplicationContext
that was running inside the Jersey container since it's not easily
accessible from the test. My initial attempt went like this:
class FooTestService implements FooService {
private static FooService delegate;
public static setDelegate(FooService delegate) {
FooTestService.delegate = delegate;
}
public Foo getAFoo() {
if (delegate != null) {
return delegate.getAFoo();
}
return null;
}
}
Then write a test context file such as:
<beans>
<bean class="com.foo.FooJaxRsResource"/>
<bean class="com.foo.FooTestService"/>
</beans>
The FooTestService satisfies the dependency from the FooJaxRsResource. Then
in the test:
@Before
public void setUp() {
... // load the test context in the Jersey container
FooService mockedService = mock(FooService.class);
FooTestService.setDelegate(mockedService);
}
Since, for the test, the container is running in the same VM, the static
method call will inject the mock as a delegate into the field, which is the
same field that the Spring bean has access to. This approach has worked
perfectly. The only downside is that you end up with a profusion of these
delegating classes if you have a lot of different services to test. My
project got up to about 20 different implementations that all looked
basically the same. Right now, I'm working on ironing out the kinks from a
more generic approach that uses dynamic proxies to generate these things on
demand. Now the context file looks like:
<beans>
<bean class="com.foo.FooJaxRsResource"/>
<bean class="com.foo.DelegatingProxyFactoryBean">
<property name="proxyInterface" value="com.foo.FooService"/>
</bean>
</beans>
The DelegatingProxyFactoryBean is a Spring FactoryBean (
http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/beans/factory/FactoryBean.html).
Internally, it creates a dynamic proxy (
http://download.oracle.com/javase/6/docs/api/java/lang/reflect/Proxy.html)
using the given interface and returns that as a Spring bean. The proxy has
an InvocationHandler that delegates to a mock object, which is provided by
the test via a static method call again, this time *before* the application
context is started. Otherwise, it wouldn't be available when needed. So the
new test code looks like:
@Before
public void setUp() {
FooService mockedService = mock(FooService.class);
DelegatingProxyFactoryBean.addDelegate(FooService.class, mockedService);
... // load the test context in the Jersey container
}
We're very happy with this approach, even in its preliminary form.
On Thu, Jan 13, 2011 at 10:22 AM, Ryan Stewart <zzantozz_at_gmail.com> wrote:
> I've been developing a way to deal with this, too, and I went a completely
> different direction. My goal was to be able to run the JAX-RS resource in
> the Jersey container but mock whatever bean(s) that it depends on in order
> to write very isolated tests just against the REST calls provided by the
> resource. As a trivial example:
> @Path("/foo")
> class FooJaxRsResource {
> @Autowired
> private FooService service;
>
>
> @Path("/something")
>
> }
>
> On Thu, Jan 13, 2011 at 3:42 AM, Colin Vipurs <zodiaczx6_at_gmail.com> wrote:
>
>> This is something I tried to do a while back and this is what I came
>> up with. It seems neater than the solution in the link but it's not
>> been battle hardened yet.
>>
>> public class MyIntegrationTest extends JerseyTest {
>> private MockService mockService;
>>
>> @Override
>> protected AppDescriptor configure() {
>> mockService = new MockService();
>> MockableContext.addMock("myService", mockService);
>>
>> return new WebAppDescriptor.Builder("com.shazam.rest")
>> .contextPath("/root")
>> .contextParam("contextConfigLocation",
>> "classpath:applicationContext.xml")
>> .contextParam("contextClass",
>> "com.company.MockableContext")
>> .servletClass(SpringServlet.class)
>> .contextListenerClass(ContextLoaderListener.class)
>> .requestListenerClass(RequestContextListener.class)
>> .build();
>> }
>> }
>>
>> public class MockableContext extends XmlWebApplicationContext {
>> private static Map<String, Object> mocks = new HashMap<String,
>> Object>();
>>
>> @Override
>> protected DefaultListableBeanFactory createBeanFactory() {
>> return new MockableBeanFactory();
>> }
>>
>> private class MockableBeanFactory extends
>> DefaultListableBeanFactory {
>> @Override
>> public Object getBean(String name) throws BeansException {
>> Object obj = mocks.get(name);
>> if (obj != null) {
>> return obj;
>> }
>> return super.getBean(name);
>> }
>> }
>>
>> public static void addMock(String id, Object mock) {
>> mocks.put(id, mock);
>> }
>>
>> public static void reset() {
>> mocks.clear();
>> }
>> }
>>
>>
>> On Wed, Jan 12, 2011 at 1:22 PM, Jonathan Cook - FM&T
>> <Jonathan.Cook2_at_bbc.co.uk> wrote:
>> > That was it :)
>> >
>> > Now I would like to be able to mock out that autowired bean. I came
>> across
>> > this but it looks like a bit of work, is there anything neater?
>> >
>> http://jersey.576304.n2.nabble.com/unit-test-a-jersey-resource-with-mocked-spring-beans-td581233.html
>> >
>> > Thanks
>> >
>> >
>> > On 12/01/2011 12:33, "Jonathan Cook - FM&T" <Jonathan.Cook2_at_bbc.co.uk>
>> > wrote:
>> >
>> > Just read this:
>> >
>> http://jersey.576304.n2.nabble.com/Jersey-test-and-Spring-td5607651.html
>> >
>> > Perhaps that is the reason?
>> >
>> > Thanks
>> >
>> >
>> > On 12/01/2011 12:28, "Jonathan Cook - FM&T" <Jonathan.Cook2_at_bbc.co.uk>
>> > wrote:
>> >
>> > Hi,
>> >
>> > I have the following simple example unit test:
>> >
>> > public class TableResourceTest extends JerseyTest {
>> >
>> > public TableResourceTest() throws Exception {
>> > super(new WebAppDescriptor.Builder("bbc.forge.statsapi")
>> > .contextPath("football")
>> > .contextParam("contextConfigLocation",
>> > "classpath:applicationContext.xml")
>> > .contextParam("log4jConfigLocation",
>> > "classpath:log4j.properties")
>> > .contextParam("propertiesConfigLocation",
>> > "classpath:propertiesConfigContext.xml")
>> > .contextListenerClass(ContextLoaderListener.class)
>> > .requestListenerClass(RequestContextListener.class)
>> > .servletClass(SpringServlet.class).build());
>> > }
>> >
>> > @Test
>> > public void testTable() throws Exception {
>> >
>> > WebResource webResource = resource();
>> > String responseMsg =
>> > webResource.path("/football/table/competition/100").get(String.class);
>> > Assert.assertEquals("Some Text", responseMsg);
>> > }
>> > }
>> >
>> > However on running the test I get a NullPointerException. The resource I
>> am
>> > invoking has an AutoWired property which is in the
>> applicationContext.xml.
>> > But it appears the http container isn’t initialising spring at all. I’m
>> not
>> > really sure why. I have looked through some of the samples such as
>> > spring-annotations and it doesn’t appear to be any different.
>> >
>> > The container is initialising the resources ok so I wasn’t sure why it
>> > wasn’t picking up the applicationContext.xml. Or maybe it is and this
>> > doesn’t work with autowiring?
>> >
>> > INFO: Scanning for root resource and provider classes in the packages:
>> > bbc.forge.statsapi
>> > Jan 12, 2011 12:19:55 PM com.sun.jersey.api.core.ScanningResourceConfig
>> > logClasses
>> > INFO: Root resource classes found:
>> >
>> > Thanks
>> > Jon
>> >
>>
>>
>>
>> --
>> Maybe she awoke to see the roommate's boyfriend swinging from the
>> chandelier wearing a boar's head.
>>
>> Something which you, I, and everyone else would call "Tuesday", of course.
>>
>
>