Как я могу проверить использование области Guice в тестах?

У меня есть несколько тестов, которые я бы хотел провалить, если определенные области Guice используются некорректно. Например, @Singleton не должен иметь никаких зависимостей @RequestScoped или @TestScoped (Provider<>, конечно, нормально).

В производстве это частично решается, потому что слагаемые с привязкой синглтоны будут построены до ввода области видимости, что приведет к OutOfScopeException s. Но при разработке синглтон будет создан лениво, находясь внутри области видимости, и никаких проблем не видно.

Судя по этим два открытых вопроса, кажется, что нет простого, встроенного способа сделать это. Могу ли я достичь этого с помощью SPI? Я попытался использовать TypeListener, но не ясно, как получить зависимости заданного типа.

Ответ 1

Вот как я это сделал с бета-версией 4.0 от Guice, используя 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));
                }
            }
        }
    }
}

Ответ 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);
  }
}

TestCase

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 {

}