Unit Testing gwt-dispatch
I'm trying to write some unit tests for a gwt-dispatch service with JUnit. I'm getting the following error when stepping through the test with my debugger:
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.
I'm going to simplify the code a bit here -- hopefully I'm not stripping out anything necessary.
import junit.framework.TestCase;
import net.customware.gwt.dispatch.client.standard.StandardDispatchService;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.ServletModule;
...
public class LoggedInService开发者_运维问答Test extends TestCase {
Injector i;
StandardDispatchService service;
protected com.google.inject.Injector getInjector() {
return Guice.createInjector(new ServletModule(),
new TestServletModule(),
new ActionsHandlerModule(),
new TestDispatchModule(),
new OpenIdGuiceModule());
}
public void setUp() throws Exception {
i = getInjector();
service = i.getInstance(StandardDispatchService.class);
}
public void testNotLoggedIn() {
try {
GetProjectsResult result = (GetProjectsResult) service.execute(new GetProjectsAction());
result.getSizeOfResult();
} catch (Exception e) {
fail();
}
}
}
The service request is indeed supposed to be going through a GuiceFilter, and it looks like that filter is not being set.
Any ideas on what other setup needs to be done to register the filter?
The problem is just what it states. You are trying to access a scoped object, but you are not currently in the scope. Most likely, your test is asking the injector for a RequestScoped
object or an object that has a RequestScoped
object in the injection dependency tree, but the test didn't do anything to enter the scope.
Binding the GuiceFilter
in the test doesn't help, because your test isn't trying to send an HttpServletRequest
through GuiceFilter
to a servlet.
The best option would be to unit test your code. Create your classes in isolation, injecting mocks.
Assuming you want to do some kind of integration test, you have three options:
- Have your test install a test module that called
bindScope(RequestScoped.class, new FakeScope)
. TheFakeScope
class would implementScope
and have methods to enter and exit the scope. You may have to "seed" the scope with fake implementations of objects you depend on. See the Guice CustomScopes wiki page. This is the best option for integration tests, IMHO - Use
ServletScopes.scopeRequest
(Javadoc) to run part of the test code inside of a simulated request scope. This gets a bit ugly since you need to pass aCallable
. - Do a full end-to-end test. Start your server and send it requests using Selenium. It's really hard to get good coverage this way, so I would leave this to things that you really need a browser to test.
Things might get a bit messy if the class you are testing depends indirectly on HttpServletRequest
or HttpServletResponse
. These classes can be challenging to setup correctly. Most of your classes should not depend on the servlet classes directly or indirectly. If that is not the case, you are either doing something wrong or you need to find a good action framework that allows you have most of your code not depend on these classes.
Here's an example of approach 1, using SimpleScope
from the Guice CustomScopes wiki page:
public class LoggedInServiceTest extends TestCase {
private final Provider<StandardDispatchService> serviceProvider;
private final SimpleScope fakeRequestScope = new SimpleScope();
private final HttpServletRequest request = new FakeHttpServletRequest();
protected Injector createInjector() {
return Guice.createInjector(new FakeRequestScopeModule(),
new LoggedInServiceModule();
}
@Override
protected void setUp() throws Exception {
super.setUp();
Injector injector = createInjector();
scope.enter();
serviceProvider = injector.getProvider(StandardDispatchService.class);
}
@Override
protected void tearDown() throws Exception {
fakeRequestScope.exit()
super.tearDown();
}
public void testNotLoggedIn() {
fakeRequestScope.enter();
// fill in values of request
fakeRequestScope.seed(FakeHttpServletRequest.class, request);
StandardDispatchService service = serviceProvider.get();
GetProjectsAction action = new GetProjectsAction();
try {
service.execute(action);
fail();
} catch (NotLoggedInException expected) {
}
}
private class FakeRequestScopeModule extends AbstractModule() {
@Override
protected void configure() {
bind(RequestScoped.class, fakeRequestScope);
bind(HttpServletRequest.class)
.to(FakeHttpServletRequest.class)
.in(RequestScoped.class)
}
}
}
Write an AppSession interface and two implementations: HttpAppSession and MockAppSession. Make your server-side handlers depend on AppSession and not on HttpSession directly.
Use Guice to inject HttpSession into HttpAppSession. That's the one you'll use in production, and for actually running your app. within a real servlet container.
The MockAppSession should not depend on HttpSession, nor HttpServletRequest, nor any other Guice Http scope. That's the one you'll use during testing.
Now, your Guice module should inject an AppSession implementation as follows:
bind(AppSession.class).to(MockAppSession.class)
bind(MockAppSession.class).in(Singleton.class)
That'll sort you out.
精彩评论