如何在测试中验证Guice作用域的使用?

10

我有一些测试,如果某些Guice作用域使用不正确,我希望它们失败。例如,@Singleton 不应该有任何@RequestScoped@TestScoped的依赖项(当然,Provider<>是可以的)。

在生产环境中,这部分问题已经得到解决,因为急切绑定的单例将在进入作用域之前构建,导致OutOfScopeException。但在开发阶段,单例将在作用域内惰性创建,并且没有任何问题显现。

根据这些两个未解决的问题来看,似乎没有简单的内置方法可以做到这一点。我能否使用SPI实现这一点?我尝试使用TypeListener,但不清楚如何获取给定类型的依赖项。

2个回答

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

作用域对象

@RequestScoped
public class RequestScopedObject extends Object {

}

@SessionScoped
public class SessionScopedObject extends Object {

}

@Singleton
public class SingletonScopedObject extends Object {

}

2
谢谢,这确实是拼图的一部分。为了测试绑定是否为单例,还有Scopes.isSingleton()。我卡住的另一部分是拼图的另一部分。我已经找到了一个Guice 4的解决方案,即将发布,但如果可能的话,我仍然希望有一个Guice 3的解决方案。 - Tavian Barnes

1
这是我使用Guice 4.0 beta和ProvisionListener实现的方法。我尝试过TypeListener,但似乎TypeListener在Guice具有该类型依赖项的绑定之前被调用。这会导致异常,甚至在某些情况下死锁。
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));
                }
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接