单元测试 gwt-dispatch

发布于 2024-10-12 00:22:06 字数 1347 浏览 5 评论 0原文

我正在尝试使用 JUnit 为 gwt-dispatch 服务编写一些单元测试。使用调试器单步执行测试时出现以下错误:

自定义提供程序中出现错误,com.google.inject.OutOfScopeException:无法访问限定范围的对象。要么我们当前不在 HTTP Servlet 请求中,要么您可能忘记应用 com.google.inject.servlet.GuiceFilter 作为此请求的 servlet 过滤器。

我将在这里稍微简化一下代码——希望我不会删除任何必要的东西。

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 LoggedInServiceTest 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();
    }
}
}

服务请求确实应该通过 GuiceFilter,但看起来该过滤器尚未设置。

关于注册过滤器需要进行哪些其他设置有什么想法吗?

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 LoggedInServiceTest 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

听不够的曲调 2024-10-19 00:22:06

问题正是它所指出的。您正在尝试访问有作用域的对象,但您当前不在该作用域内。最有可能的是,您的测试向注入器询问 RequestScoped 对象或在注入依赖树中具有 RequestScoped 对象的对象,但测试没有执行任何操作进入范围。

在测试中绑定 GuiceFilter 没有帮助,因为您的测试并没有尝试通过 GuiceFilterHttpServletRequest 发送到 servlet。

最好的选择是对代码进行单元测试。隔离地创建类,注入模拟。

假设您想要进行某种集成测试,您有三个选择:

  1. 让您的测试安装一个名为 bindScope(RequestScoped.class, new FakeScope) 的测试模块。 FakeScope 类将实现 Scope 并具有进入和退出范围的方法。您可能必须使用您所依赖的对象的虚假实现来“播种”范围。请参阅 Guice CustomScopes wiki 页面。这是集成测试的最佳选择,恕我直言,
  2. 使用 ServletScopes.scopeRequest (Javadoc) 在模拟请求范围内运行部分测试代码。这有点难看,因为您需要传递一个 Callable。
  3. 进行完整的端到端测试。启动您的服务器并使用 Selenium 向其发送请求。通过这种方式很难获得良好的覆盖范围,因此我会将其留给您真正需要浏览器来测试的事情。

如果您正在测试的类间接依赖于 HttpServletRequestHttpServletResponse,事情可能会变得有点混乱。正确设置这些类可能具有挑战性。大多数类不应直接或间接依赖于 servlet 类。如果情况并非如此,那么您要么做错了什么,要么需要找到一个好的操作框架,让您的大部分代码不依赖于这些类。

以下是方法 1 的示例,使用 Guice 中的 SimpleScope CustomScopes 维基页面

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)
    }
  }
}

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:

  1. Have your test install a test module that called bindScope(RequestScoped.class, new FakeScope). The FakeScope class would implement Scope 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
  2. 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 a Callable.
  3. 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)
    }
  }
}
枯叶蝶 2024-10-19 00:22:06

编写一个AppSession接口和两个实现:HttpAppSession和MockAppSession。
让您的服务器端处理程序依赖于 AppSession 而不是直接依赖于 HttpSession。

  • 使用 Guice 将 HttpSession 注入 HttpAppSession。这是您将在生产中使用并实际运行您的应用程序的那个。在真实的 servlet 容器中。

  • MockAppSession 不应依赖于 HttpSession、HttpServletRequest 或任何其他 Guice Http 范围。这就是您将在测试期间使用的那个。

现在,您的 Guice 模块应该注入一个 AppSession 实现,如下所示:

bind(AppSession.class).to(MockAppSession.class)
bind(MockAppSession.class).in(Singleton.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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文