2013-08-27 3 views
10

Ho alcuni test che mi piacerebbe avere esito negativo se determinati ambiti di Guice sono usati in modo errato. Ad esempio, un @Singleton non dovrebbe avere alcuna dipendenza @RequestScoped o @TestScoped (Provider<> s, ovviamente).Come posso convalidare l'utilizzo dell'oscilloscopio di Guice nei test?

In produzione, questo viene parzialmente risolto perché i singleton con vincolo di volontà verranno costruiti prima dell'inserimento dell'ambito, ottenendo OutOfScopeException s. Ma nello sviluppo, il singleton verrà creato pigramente mentre è all'interno dello scope e nessun problema è evidente.

A giudicare dal numero thesetwo problemi aperti, sembra che non ci sia un modo semplice e integrato per farlo. Posso ottenere questo usando lo SPI? Ho provato a utilizzare un TypeListener ma non è chiaro come ottenere le dipendenze di un determinato tipo.

risposta

1

Ecco come ho accom lo ha completato con la 4.0 beta di Guice, usando ProvisionListener. Ho provato TypeListener ma sembra che TypeListener s venga chiamato prima che Guice abbia necessariamente binding per le dipendenze di quel tipo. Ciò ha causato eccezioni e persino un deadlock in un caso.

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

questo non è un problema banale, ma sicuramente è una buona domanda! Potrebbe esserci un tester per i problemi di binding della portata che hai menzionato. Penso che potrei fare un corridore Junit per generare avvertimenti con una pratica vincolante errata. Aggiornerò questo post in seguito con esso.

Per ora c'è un esempio su come ottenere gli ambiti vincolanti.

modulo

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

mirino oggetti

@RequestScoped 
public class RequestScopedObject extends Object { 

} 

@SessionScoped 
public class SessionScopedObject extends Object { 

} 

@Singleton 
public class SingletonScopedObject extends Object { 

} 
+1

Grazie, questo è sicuramente un pezzo del puzzle. Per verificare se un legame è un singleton o no, c'è anche 'Scopes.isSingleton()'. L'altra parte del puzzle è dove ero bloccato. Ho trovato una soluzione di Guice 4 che sto per pubblicare, ma mi piacerebbe comunque una soluzione di Guice 3, se possibile. –