Search code examples
javaunit-testingjunitguiceguice-servlet

Testing a project using Guice Servlet: How to bind out-of-scope RequestScoped instances?


We have a Java web application that is instantiated using Guice and the Guice Servlet extension. The application also includes Quartz jobs that are also instantiated by Guice. I want to write unit test for these jobs.

The job classes depend on other other classes from our productive code which require a Provider<HttpServletRequest>. In the productive setup, the jobs can be successfully instantiated by Guice, and the jobs work as expected because they never call any code on their their collaborators that triggers a get on the servlet request provider. Such a get call would fail because the jobs are executed by some worker thread, and not as part of an HTTP request to the servlet.

Now my question is how to set up Guice in a JUnit test so that I get the same behaviour, i.e. the job classes can be instantiated, but all attempts to use any out-of-scope @RequestScoped objects would fail.


There are two straigtforward solutions, but they don't work for me.

If I don't bind any HttpServletRequest, I get an error when trying to instantiate the job in the test setup:

com.google.inject.CreationException: Guice creation errors:

1) No implementation for javax.servlet.http.HttpServletRequest was bound.
  while locating com.google.inject.Provider<javax.servlet.http.HttpServletRequest>

If on the other hand I just bind mock instances, e.g. with

@Override
protected void configure() {
    bind(HttpServletRequest.class).toInstance(mock(HttpServletRequest.class));
}

then the job can be instantiated, but I no longer get a test error in case the job makes use of the servlet request instance. So how can I create a binding so that Guice is able to instantiate providers, but any use of the providers would fail?


Solution

  • After a few attempts, I found out how to bind @RequestScoped objects in a test module:

    @Override
    protected void configure() {
        bindScope(RequestScoped.class, ServletScopes.REQUEST);
    
        bind(ServletRequest.class).toProvider(unusableProvider(ServletRequest.class)).in(RequestScoped.class);
        bind(HttpServletRequest.class).toProvider(unusableProvider(HttpServletRequest.class)).in(RequestScoped.class);
        bind(ServletResponse.class).toProvider(unusableProvider(ServletResponse.class)).in(RequestScoped.class);
        bind(HttpServletResponse.class).toProvider(unusableProvider(HttpServletResponse.class)).in(RequestScoped.class);
    }
    
    private static <T> Provider<T> unusableProvider(final Class<T> type) {
        return new Provider<T>() {
            @Override
            public T get() {
                throw new IllegalStateException("Unexpected call to provider of " + type.getSimpleName());
            }
        };
    }
    

    The tests never enter the request scope (i.e. there are no calls to ServletScope.scopeRequest), so all attempts to obtain the RequestScoped objects in the tests would result in exactly the same error as in production:

    com.google.inject.ProvisionException: Guice provision errors:
    
    1) Error in custom provider, com.google.inject.OutOfScopeException: Cannot access scoped object. Either we are not currently inside an HTTP Servlet request, or you may have forgotten to apply com.google.inject.servlet.GuiceFilter as a servlet filter for this request.
    

    Binding an "unusable provider" isn't actually necessary - I just did it as an extra safety net. This means that this solution should also work for classes which aren't explicitly bound but which are annotated with @RequestScoped and automatically discovered by Guice.