如何在测试中验证Guice范围的用法?

时间:2013-08-27 19:59:33

标签: java guice guice-3

如果某些Guice范围使用不正确,我有一些测试我想失败。例如,@Singleton不应该有任何@RequestScoped@TestScoped个依赖项(Provider<> s当然可以。)

在生产中,这部分解决了,因为在输入范围之前将构建热切的单体,从而产生OutOfScopeException s。但是在开发过程中,单例将在范围内懒洋洋地创建,并且没有明显的问题。

根据these two未解决的问题判断,似乎没有简单的内置方法可以做到这一点。我可以使用SPI实现这一目标吗?我尝试使用TypeListener,但不清楚如何获取给定类型的依赖项。

2 个答案:

答案 0 :(得分:2)

这不是一个小问题,但绝对是一个很好的问题!可能有一个测试人员提到你提到的范围绑定问题。我想我可以让Junit跑步者用错误的绑定练习产生警告。我稍后会用它来更新这篇文章。

现在有一个例子来说明如何获取绑定范围。

<强>模块

public class ScopeTestModel extends ServletModule {

  @Override
  protected void configureServlets() {
    super
        .configureServlets();
    bind(Key.get(Object.class, Names.named("REQ1"))).to(Object.class).in(ServletScopes.REQUEST);
    bind(Key.get(Object.class, Names.named("REQ2"))).to(RequestScopedObject.class);

    bind(Key.get(Object.class, Names.named("SINGLETON1"))).to(Object.class).asEagerSingleton();
    bind(Key.get(Object.class, Names.named("SINGLETON2"))).to(Object.class).in(Scopes.SINGLETON);
    bind(Key.get(Object.class, Names.named("SINGLETON3"))).to(SingletonScopedObject.class);

    bind(Key.get(Object.class, Names.named("SESS1"))).to(Object.class).in(ServletScopes.SESSION);
    bind(Key.get(Object.class, Names.named("SESS2"))).to(SessionScopedObject.class);
  }
}

<强>测试用例

public class TestScopeBinding {

  private Injector injector = Guice.createInjector(new ScopeTestModel());

  @Test
  public void testRequestScope() throws Exception {
    Binding<Object> req1 = injector.getBinding(Key.get(Object.class, Names.named("REQ1")));
    Binding<Object> req2 = injector.getBinding(Key.get(Object.class, Names.named("REQ2")));

    Scope scope1 = getScopeInstanceOrNull(req1);
    Scope scope2 = getScopeInstanceOrNull(req2);

    Assert.assertEquals(ServletScopes.REQUEST,scope1);
    Assert.assertEquals(ServletScopes.REQUEST,scope2);
  }

  @Test
  public void testSessionScope() throws Exception {
    injector.getAllBindings();
    Binding<Object> sess1 = injector.getBinding(Key.get(Object.class, Names.named("SESS1")));
    Binding<Object> sess2 = injector.getBinding(Key.get(Object.class, Names.named("SESS2")));

    Scope scope1 = getScopeInstanceOrNull(sess1);
    Scope scope2 = getScopeInstanceOrNull(sess2);

    Assert.assertEquals(ServletScopes.SESSION,scope1);
    Assert.assertEquals(ServletScopes.SESSION,scope2);
  }

  @Test
  public void testSingletonScope() throws Exception {
    injector.getAllBindings();
    Binding<Object> sng1 = injector.getBinding(Key.get(Object.class, Names.named("SINGLETON1")));
    Binding<Object> sng2 = injector.getBinding(Key.get(Object.class, Names.named("SINGLETON2")));
    Binding<Object> sng3 = injector.getBinding(Key.get(Object.class, Names.named("SINGLETON3")));

    Scope scope1 = getScopeInstanceOrNull(sng1);
    Scope scope2 = getScopeInstanceOrNull(sng2);
    Scope scope3 = getScopeInstanceOrNull(sng3);

    Assert.assertEquals(Scopes.SINGLETON,scope1);
    Assert.assertEquals(Scopes.SINGLETON,scope2);
    Assert.assertEquals(Scopes.SINGLETON,scope3);
  }

  private Scope getScopeInstanceOrNull(final Binding<?> binding) {
    return binding.acceptScopingVisitor(new DefaultBindingScopingVisitor<Scope>() {

      @Override
      public Scope visitScopeAnnotation(Class<? extends Annotation> scopeAnnotation) {
        throw new RuntimeException(String.format("I don't know how to handle the scopeAnnotation: %s",scopeAnnotation.getCanonicalName()));
      }

      @Override
      public Scope visitNoScoping() {
          if(binding instanceof LinkedKeyBinding) {
            Binding<?> childBinding = injector.getBinding(((LinkedKeyBinding)binding).getLinkedKey());
            return getScopeInstanceOrNull(childBinding);
          }
        return null;
      }

      @Override
      public Scope visitEagerSingleton() {
        return Scopes.SINGLETON;
      }

      public Scope visitScope(Scope scope) {
        return scope;
      }
    });
  }
}

Scoped objects

@RequestScoped
public class RequestScopedObject extends Object {

}

@SessionScoped
public class SessionScopedObject extends Object {

}

@Singleton
public class SingletonScopedObject extends Object {

}

答案 1 :(得分:1)

以下是我使用ProvisionListener使用Guice的4.0 beta完成此操作的方法。我尝试了TypeListener但似乎在Guice必然具有该类型依赖项的绑定之前调用TypeListener。这导致异常,甚至在一个案例中出现僵局。

private static class ScopeValidator implements ProvisionListener {
    private @Inject Injector injector;
    private @Inject WhateverScope scope;

    @Override
    public <T> void onProvision(ProvisionInvocation<T> provision) {
        if (injector == null) {
            // The injector isn't created yet, just return. This isn't a
            // problem because any scope violations will be caught by
            // WhateverScope itself here (throwing an OutOfScopeException)
            return;
        }

        Binding<?> binding = provision.getBinding();
        Key<?> key = binding.getKey();

        if (Scopes.isSingleton(binding) && binding instanceof HasDependencies) {
            Set<Dependency<?>> dependencies = ((HasDependencies) binding).getDependencies();

            for (Dependency<?> dependency : dependencies) {
                Key<?> dependencyKey = dependency.getKey();
                Binding<?> dependencyBinding = injector.getExistingBinding(dependencyKey);

                if (dependencyBinding != null && Scopes.isScoped(dependencyBinding, whateverScope, WhateverScoped.class)) {
                    throw new ProvisionException(String.format(
                            "Singleton %s depends on @WhateverScoped %s",
                            key, dependencyKey));
                }
            }
        }
    }
}