So, we created a simple sandbox representation of this problem and then discovered a way to effectively test that the Guice configuration was correct. We must give credit to the authors of Guice for making their test available, this is how we figured it out.
First let us show you the classes that I want to test, note this is highly contrived for the purpose of clarity;
The following servlet is simply in place to allow us to have a servlet with a Request Scoped dependency in a Jersey service.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package jerseyandguice; | |
import javax.servlet.http.HttpServlet; | |
import javax.ws.rs.Path; | |
import com.google.inject.Inject; | |
import com.google.inject.Singleton; | |
@Path("/jg") | |
@Singleton | |
public class BogoServlet extends HttpServlet { | |
private static final long serialVersionUID = 1L; | |
private Dependancy dependancy; | |
@Inject | |
public BogoServlet(Dependancy dep) { | |
dependancy = dep; | |
} | |
public Dependancy getDependancy() { | |
return dependancy; | |
} | |
} |
The Dependancy is simply an implementation of an interface, neither of which contain any methods;
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package jerseyandguice; | |
public class DefaultDependancy implements Dependancy { | |
} |
And we have a custom module for creating the Dependency. Note that the Dependancy.class is scoped as @RequestScoped;
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package jerseyandguice; | |
import com.google.inject.AbstractModule; | |
import com.google.inject.servlet.RequestScoped; | |
public class DependancyModule extends AbstractModule { | |
@Override | |
protected void configure() { | |
bind(Dependancy.class).to(DefaultDependancy.class).in(RequestScoped.class); | |
} | |
} |
And finally, the GuiceServletContextListener that would be used by the Servlet container to do the actual injections at runtime;
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package jerseyandguice; | |
import com.google.inject.Guice; | |
import com.google.inject.Injector; | |
import com.google.inject.servlet.GuiceServletContextListener; | |
import com.google.inject.servlet.ServletModule; | |
public class JGServletContextListener extends GuiceServletContextListener { | |
private final ServletModule customServletModule = new ServletModule() { | |
@Override | |
protected void configureServlets() { | |
serve("/jg/*").with(BogoServlet.class); | |
} | |
}; | |
@Override | |
protected Injector getInjector() { | |
return Guice.createInjector(new DependancyModule(), customServletModule); | |
} | |
} |
Now that you have the context, here's how we solved the problem. The GuiceFilter used by Guice Servlet support conducts the injection activity for the servlet when a request is executed. If you attempt to get, from the injector, any injected instance that is RequestScoped you will get an exception from the Injector indicating that you've requested a resource outside the scope of a Request. It's your own fault really :-).
So, by contriving a filter chain of assertions you can determine if Guice has actually injected the dependencies correctly. Here's how;
First we created this Assertion class;
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package jerseyandguice; | |
import static org.testng.Assert.assertNotNull; | |
import static org.testng.Assert.assertTrue; | |
import java.util.concurrent.atomic.AtomicReference; | |
import javax.servlet.FilterChain; | |
import javax.servlet.ServletRequest; | |
import javax.servlet.ServletResponse; | |
import javax.servlet.http.HttpServletRequest; | |
import org.jmock.Expectations; | |
import org.jmock.Mockery; | |
import com.google.inject.Injector; | |
import com.google.inject.servlet.GuiceFilter; | |
public class GuicedServletAssertion { | |
public static void assertProvisioningType(final Injector injector, | |
final Class<?> requestedType, final Class<?> expectedImplementation) | |
throws Exception { | |
applyFilterChainAssertion(new FilterChain() { | |
public void doFilter(ServletRequest servletRequest, | |
ServletResponse servletResponse) { | |
assertTrue(requestedType | |
.isInstance(injector.getInstance(requestedType))); | |
assertTrue(expectedImplementation.isInstance(injector | |
.getInstance(requestedType))); | |
} | |
}); | |
} | |
public static void assertInstantiablity(final Injector injector, | |
final Class<?> clazz) throws Exception { | |
applyFilterChainAssertion(new FilterChain() { | |
public void doFilter(ServletRequest servletRequest, | |
ServletResponse servletResponse) { | |
assertNotNull(injector.getInstance(clazz)); | |
} | |
}); | |
} | |
public static void applyFilterChainAssertion(FilterChain filterChain) | |
throws Exception { | |
Mockery context = new Mockery(); | |
GuiceFilter filter = new GuiceFilter(); | |
final HttpServletRequest request = context.mock(HttpServletRequest.class); | |
context.checking(new Expectations() { | |
{ | |
allowing(request).getAttribute(with(any(String.class))); | |
will(returnValue(null)); | |
allowing(request).setAttribute(with(any(String.class)), | |
with(any(Class.class))); | |
oneOf(request).getServletPath(); | |
} | |
}); | |
filter.doFilter(request, null, filterChain); | |
} | |
public static <T> T expose(final Injector injector, final Class<?> clazz) | |
throws Exception { | |
final AtomicReference<T> ref = new AtomicReference<T>(); | |
applyFilterChainAssertion(new FilterChain() { | |
@SuppressWarnings("unchecked") | |
public void doFilter(ServletRequest servletRequest, | |
ServletResponse servletResponse) { | |
ref.set((T) injector.getInstance(clazz)); | |
} | |
}); | |
return ref.get(); | |
} | |
} |
The assertProvisioningType method allows you to assert that an instance is provided for any type request and that the provided instance is of the appropriate type.
The assertInstantiablity method allows you to assert that an instance is provided for a requested type.
The applyFilterChainAssertion method executes the given FilterChain using the GuiceFilter as the head of the chain. JMock is used in this case to support our current project configuration (Note: We'll be rewriting this with Mockito soon enough.) When you call this method with a FilterChain full of assertions they will have access to the Injector and will be within the Request scope, so you can easily make assertions about them without getting the nasty exception pictured here;
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | |
while locating jerseyandguice.Dependancy | |
1 error | |
at com.google.inject.InjectorImpl$4.get(InjectorImpl.java:767) | |
at com.google.inject.InjectorImpl.getInstance(InjectorImpl.java:793) | |
at jerseyandguice.GuiceWiringTest.shouldFailToGetDependancyBecauseOfScope(GuiceWiringTest.java:62) | |
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) | |
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) | |
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) | |
at java.lang.reflect.Method.invoke(Method.java:597) | |
at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:73) | |
at org.testng.internal.Invoker.invokeMethod(Invoker.java:667) | |
at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:840) | |
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1141) | |
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:135) | |
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:119) | |
at org.testng.TestRunner.runWorkers(TestRunner.java:1108) | |
at org.testng.TestRunner.privateRun(TestRunner.java:737) | |
at org.testng.TestRunner.run(TestRunner.java:596) | |
at org.testng.SuiteRunner.runTest(SuiteRunner.java:315) | |
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:310) | |
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:272) | |
at org.testng.SuiteRunner.run(SuiteRunner.java:221) | |
at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52) | |
at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86) | |
at org.testng.TestNG.runSuitesSequentially(TestNG.java:946) | |
at org.testng.TestNG.runSuitesLocally(TestNG.java:883) | |
at org.testng.TestNG.run(TestNG.java:814) | |
at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:89) | |
at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:144) | |
Caused by: 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. | |
at com.google.inject.servlet.GuiceFilter.getContext(GuiceFilter.java:132) | |
at com.google.inject.servlet.GuiceFilter.getRequest(GuiceFilter.java:118) | |
at com.google.inject.servlet.ServletScopes$1$1.get(ServletScopes.java:48) | |
at com.google.inject.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:48) | |
at com.google.inject.InjectorImpl$4$1.call(InjectorImpl.java:758) | |
at com.google.inject.InjectorImpl.callInContext(InjectorImpl.java:804) | |
at com.google.inject.InjectorImpl$4.get(InjectorImpl.java:754) |
The expose method allows you to directly fetch a Request Scoped instance from the injector for additional testing.
Here is the test suite from our contrived example;
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package jerseyandguice; | |
import static jerseyandguice.GuicedServletAssertion.assertInstantiablity; | |
import static jerseyandguice.GuicedServletAssertion.assertProvisioningType; | |
import static jerseyandguice.GuicedServletAssertion.expose; | |
import static org.testng.Assert.assertNotNull; | |
import org.testng.annotations.BeforeClass; | |
import org.testng.annotations.Test; | |
import com.google.inject.Injector; | |
import com.google.inject.ProvisionException; | |
public class GuiceWiringTest { | |
private Injector injector; | |
@BeforeClass | |
public void guicify() { | |
injector = new JGServletContextListener().getInjector(); | |
} | |
@Test | |
public void shouldCreateBogoServlet() throws Exception { | |
assertInstantiablity(injector, BogoServlet.class); | |
} | |
@Test(expectedExceptions=ProvisionException.class) | |
public void shouldFailToCreateDependancyBecauseOfScope() { | |
injector.getInstance(Dependancy.class); | |
} | |
@Test | |
public void shouldCreateDependancy() throws Exception { | |
assertInstantiablity(injector, Dependancy.class); | |
} | |
@Test | |
public void shouldProvideDefaultDependancyForDependancy() throws Exception { | |
assertProvisioningType(injector, Dependancy.class, DefaultDependancy.class); | |
} | |
@Test | |
public void shouldExposeBogoServlet() throws Exception { | |
BogoServlet bogoServlet = expose(injector, BogoServlet.class); | |
assertNotNull(bogoServlet); | |
assertNotNull(bogoServlet.getDependancy()); | |
} | |
@Test | |
public void shouldWireDependancyIntoBogoServlet() { | |
BogoServlet bogoServlet = (BogoServlet) injector.getInstance(BogoServlet.class); | |
assertNotNull(bogoServlet.getDependancy()); | |
} | |
@Test | |
public void shouldProvideDependancy() { | |
assertNotNull(injector.getProvider(Dependancy.class)); | |
} | |
@Test | |
public void shouldProvideBogoServlet() { | |
assertNotNull(injector.getProvider(BogoServlet.class)); | |
} | |
} |
Hope this helps someone.