Tuesday, July 3, 2012

Jersey Unit Testing with Guice and EasyMock

This post will expand on my earlier postings on Guice integration in Jersey here
and here. Jersey has a little known but powerful in-memory test framework. Using
an in-memory test container along with Guice allows you to unit test resource
lookup without the expense of full HTTP protocol handling. This post shows you
how.

First, a sample software stack in Maven POM:
   <properties>
       <guice.version>3.0</guice.version>
       <jersey.version>1.12</jersey.version>
       <easymock.version>3.1</easymock.version>
   </properties>
  <dependencies>
    <dependency>
        <groupId>com.sun.jersey</groupId>
        <artifactId>jersey-server</artifactId>
        <version>${jersey.version}</version>
    </dependency>
    <dependency>
        <groupId>com.sun.jersey.contribs</groupId>
        <artifactId>jersey-guice</artifactId>
        <version>${jersey.version}</version>
    </dependency>
   <dependency>
      <groupId>com.google.inject.extensions</groupId>
      <artifactId>guice-assistedinject</artifactId>
      <version>${guice.version}</version>
   </dependency>      
    <dependency>
        <groupId>com.sun.jersey.jersey-test-framework</groupId>
        <artifactId>jersey-test-framework-inmemory</artifactId>
        <version>${jersey.version}</version>
        <scope>test</scope>
    </dependency>
   <dependency>
       <groupId>org.easymock</groupId>
       <artifactId>easymock</artifactId>
       <version>${easymock.version}</version>
       <scope>test</scope>
   </dependency>  
  </dependencies>  

We will now work through unit-testing a sub-resource locator example from my
last post. The resource classes are reproduced here for convinence:
// In BarResource.java file
class BarResource {
   @GET Response get();
}
 
// In FooResource.java file
import com.google.inject.Provider;
@Path("/foo")
class FooResource {
   private final Provider barProvider;
 
   @Inject 
   FooResource(final Provider barProvider) {
      this.barProvider = barProvider;
   }
   
   @Path("bar") 
  @Produces(MediaType.APPLICATION_JSON)
   public Response getBar() {
      // Client request /bar will will be redirected
      //to BarResource
      BarResource bar = barProvider.get();
      bar.get();
   }
}
Our goal is to mock the sub-resource BarResource so we can uni-test resource lookup in FooResource without launching a full-scale HTTP client and server. How do we do this? The trick lies in Jerey's InMemoryTestContainerFactory. Unfortunately, it is not obvious that you can provide your own IoC container with this Factory. You only need to make one line change in the start() method.

Change:

webApp.initiate(resourceConfig);
To:
webApp.initiate(resourceConfig, new GuiceComponentProviderFactory(resourceConfig, injector));
We would have preferred InMemoryTestContainerFactory to be made extensible so we can just pass-in our injector. But we make do for now by creating our own GuiceInMmoryTestContainerFactory class based on the InMemoryTestContainerFactory code with this one line change. I will only show a skeleton implementation here to save space:
public final class GuiceInMemoryTestContainerFactory implements
        TestContainerFactory {

    private final Injector injector;

    public GuiceInMemoryTestContainerFactory(final Injector injector) {
        this.injector = injector;
    }

    @Override
    public Class<LowLevelAppDescriptor> supports() {
        return LowLevelAppDescriptor.class;
    }

    @Override
    public TestContainer create(final URI baseUri, final AppDescriptor ad){
        if (!(ad instanceof LowLevelAppDescriptor)) {
            throw new IllegalArgumentException(
                    "The application descriptor must be an instance of LowLevelAppDescriptor");
        }

        return new GuiceInMemoryTestContainer(baseUri, (LowLevelAppDescriptor) ad, injector);
        
    }

    /**
     * The class defines methods for starting/stopping an in-memory test container,
     * and for running tests on the container.
     */
    private static final class GuiceInMemoryTestContainer implements TestContainer {

        // Copy other fields from InMemoryTestContainer here.
        
        final Injector injector;

        /**
         * Creates an instance of {@link InMemoryTestContainer}
         * @param baseUri URI of the application
         * @param ad instance of {@link LowLevelAppDescriptor}
         */
        private GuiceInMemoryTestContainer(final URI baseUri, final LowLevelAppDescriptor ad, 
                final Injector injector) {
            // Copy other statements from InMemoryTestContainer here
            this.injector = injector;
        }

        // Copy other methods from InMemoryTestContainer here

        @Override public void start() {
            if (!webApp.isInitiated()) {
                LOGGER.info("Starting low level InMemory test container");

                webApp.initiate(resourceConfig, 
                                new GuiceComponentProviderFactory(resourceConfig, injector));
            }
        }
    }
}    
Now we use Jersey's test framework JerseyTest to write our unit test for FooResource.
The key elements are:
  1. Statically initialize a Guice injector;
  2. Use GuiceInMemoryTestContainer to initilize the test framework;
  3. Use JerseyServletModule to mock up dependencies.
Here is the code:
public class FooResourceTest extends JerseyTest {
    private static Injector injector;
    @BeforeClass public static void init() {
        injector = Guice.createInjector(new MockServletModule());
    }
    
    public FooResourceTest() {
        super(new GuiceInMemoryTestContainerFactory(injector));
    }
    
    @Test public void testGetBar() {
        BarResource barMock = injector.getInstance(BarResource.class);
        barMock.get();
        EasyMock.expectLastCall().andStubReturn(createMock(Response.class));
        EasyMock.replay(barMock);
        
        WebResource wr = resource();
        ClientResponse r = wr.path("/foo/bar").get(ClientResponse.class);
        assertNotNull(r.getStatus());
        
        EasyMock.verify(barMock);
    }
    

    private static class MockServletModule extends JerseyServletModule {
        @Override protected void configureServlets() {
            bind(FooResource.class);
        }
        
        @Provides BarResource providesBarResource() {
            BarResource barMock = createMock(BarResource.class);
            return barMock;
        }
    }
    
}
Run this test and you will be on your way to test restful interactions in a
Guice-enabled POJO fashion.

No comments:

Post a Comment