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.

3 comments:

  1. Nice Blog !
    One such issue is QuickBooks Payroll Error PS036. Due to this error, you'll not be able to work on your software. Thus, to fix these issues, call us at 1-855-977-7463 and get the best ways to troubleshoot QuickBooks queries.

    ReplyDelete
  2. Nice & Informative Blog !
    In case you are searching for the best technical services for QuickBooks, call us at QuickBooks Error 102 1-855-977-7463 and get impeccable technical services for QuickBooks. We make use of the best knowledge for solving your QuickBooks issues.

    ReplyDelete
  3. Trending a high quality hidden security cameras with free demo. Buy Camera near me at the lowest price with free demo. Visit Spy Shop Online for a wide range of hidden cameras, spy gadgets, and home security solutions. Get high-quality devices for secret recording, camera detection, and more. As a trusted wholesaler, our store is your one-stop-shop for all your surveillance needs. Start protecting your privacy today with Spy Shop Online. Get in touch at +91-9999332099 or 9999332499.

    ReplyDelete