Unit testing with MockHttpSession and Guice
Posted by David Chandler on December 14, 2009
The more I learn about Guice, the more I like it. First, I discovered I could get access to the HttpSession inside my Guice-injected service classes (which are in turn called from my Guice-injected gwt-dispatch ActionHandlers) like this:
public class ROAUserServiceImpl implements ROAUserService { @Inject Provider<HttpSession> httpSession; @Override public LoginInfo login(String requestUri) { httpSession.get().invalidate(); ... } ... }
Then came the unhappy moment that I ran my unit tests, which call ActionHandlers, all of which call ROAUserService to verify that the user is logged in. In my unit test environment, Guice cannot inject an HttpSession because my tests are not currently running in a servlet container; thus, every test fails.
I could bring in HttpUnit / ServletRunner, but that is really overkill for what I’m trying to do at this point, which is just to simulate one session attribute being set to represent the logged in user.
I could also create a mock ROAUserService and inject that in my ActionHandlers instead of the real one. But I’d prefer to use the real service for testing, and sooner or later, I’ll need to stuff something else in session, anyway.
Enter Guice Providers.
In the example above, the HttpSession is being injected by way of a Guice Provider, which in the real app, comes for free when your Guice injector includes a ServletModule. Fortunately, we can easily tell Guice to provide a mock HttpSession by using the @Provides annotation in a plain old AbstractModule (no ServletModule required):
package com.turbomanage.gwt.server.guice; import javax.servlet.http.HttpSession; import com.google.inject.AbstractModule; import com.google.inject.Provides; import com.roa.test.mock.MockHttpSession; public class DispatchTestModule extends AbstractModule { @Override protected void configure() { // application bindings here } @Provides HttpSession getMockSession() { return new MockHttpSession(); } }
I could probably snag a mock session from some other framework, but all I need to do is get and set attributes, hence:
package com.roa.test.mock; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionContext; public class MockHttpSession implements HttpSession { private Map<String, Object> attributes = new HashMap<String, Object>(); @Override public Object getAttribute(String name) { return attributes.get(name); } @Override public void setAttribute(String name, Object value) { attributes.put(name, value); } // All other methods auto-generated ... }
Now my TestCase setUp() method can simulate user login, logout, etc. by calling the real ROAUserService, which Guice provides with our mock HttpSession.
... User u = addTestUser(); Injector inj = Guice.createInjector(..., new DispatchTestModule()); ROAUserService userService = inj.getInstance(ROAUserService.class); userService.login(...);
Guice Providers can also accept user-defined annotations to allow you to select among multiple implementations of a provided class. That might be another handy testing tool…
Dhanji said
Another very simple way to mock the session is to fire a request to GuiceFilter with a mock request (which provides a mock session).
You can then configure your module with just the servlet you are trying to test, and that way the entire code path gets exercised.
Dhanji.
David Chandler said
Thanks, Dhanji. Because I’m using GWT RPC, in order to test the entire code path, I need a way to generate the RPC requests to my GWT RemoteServiceServlet, which requires GWTTestCase. The tests I’m writing about here are purely server-side unit tests , but I can definitely use this idea when I bring in GWTTestCase later on.
Arpit Tripathi said
This makes life so much easier. I replaced all of Mockito’s mock(HttpSession.class) with these real session objects. Thanks David.