2009-01-27 20 views
112

Acabo de empezar a jugar con Guice, y un caso de uso que se me ocurre es que en una prueba solo quiero anular una sola encuadernación. Creo que me gustaría usar el resto de los enlaces de nivel de producción para garantizar que todo esté configurado correctamente y para evitar la duplicación.Encuadernación primaria en Guice

Así que imaginen Tengo el siguiente módulo

public class ProductionModule implements Module { 
    public void configure(Binder binder) { 
     binder.bind(InterfaceA.class).to(ConcreteA.class); 
     binder.bind(InterfaceB.class).to(ConcreteB.class); 
     binder.bind(InterfaceC.class).to(ConcreteC.class); 
    } 
} 

Y en mi prueba sólo quiero anular InterfaceC, manteniendo InterfaceA y InterfaceB en el tacto, por lo que me gustaría algo así como:

Module testModule = new Module() { 
    public void configure(Binder binder) { 
     binder.bind(InterfaceC.class).to(MockC.class); 
    } 
}; 
Guice.createInjector(new ProductionModule(), testModule); 

también he intentado lo siguiente, sin suerte:

Module testModule = new ProductionModule() { 
    public void configure(Binder binder) { 
     super.configure(binder); 
     binder.bind(InterfaceC.class).to(MockC.class); 
    } 
}; 
Guice.createInjector(testModule); 

¿alguien sabe si es posible hacer lo que quiero o ¿estoy ladrando completamente el árbol equivocado?

--- Seguimiento: Parece que puedo lograr lo que quiero si utilizo la etiqueta @ImplementedBy en la interfaz y luego solo proporciono un enlace en el caso de prueba, que funciona bien cuando hay un 1-1 mapeo entre la interfaz y la implementación.

Además, después de discutir esto con un colega, parece que nos encaminamos por el camino de anular todo un módulo y asegurarnos de que tenemos nuestros módulos definidos correctamente. Esto parece que podría causar un problema, aunque un enlace se extravía en un módulo y necesita ser movido, por lo tanto, posiblemente se rompa una carga de pruebas ya que los enlaces ya no estarán disponibles para ser reemplazados.

+7

Me gusta la frase "ladrar el árbol equivocado": D –

Respuesta

124

Puede que esta no sea la respuesta que estás buscando, pero si estás escribiendo pruebas unitarias, probablemente no deberías estar usando un inyector y estar inyectando objetos simulados o falsos a mano.

Por otro lado, si realmente desea reemplazar una sola unión, se puede usar Modules.override(..):

public class ProductionModule implements Module { 
    public void configure(Binder binder) { 
     binder.bind(InterfaceA.class).to(ConcreteA.class); 
     binder.bind(InterfaceB.class).to(ConcreteB.class); 
     binder.bind(InterfaceC.class).to(ConcreteC.class); 
    } 
} 
public class TestModule implements Module { 
    public void configure(Binder binder) { 
     binder.bind(InterfaceC.class).to(MockC.class); 
    } 
} 
Guice.createInjector(Modules.override(new ProductionModule()).with(new TestModule())); 

Ver detalles here.

Pero como recomienda el javadoc para Modules.overrides(..), debe diseñar sus módulos de tal forma que no necesite anular los enlaces. En el ejemplo que proporcionó, puede lograr eso moviendo el enlace de InterfaceC a un módulo separado.

+7

Gracias Albert, eso me lleva de alguna manera en el camino hacia hacer lo que quiero. ¡Eso está en un lanzamiento de producción todavía tho! Y esto es para las pruebas de integración, no para las pruebas unitarias, por lo que deseo asegurarme de que todo lo demás se construya correctamente. – tddmonkey

+1

He agregado un ejemplo concreto al código. ¿Te lleva más lejos? – albertb

+1

A menos que me equivoque, 'ovveride' pierde el 'Escenario' apropiado mientras lo hace (es decir, DESARROLLO se usa sistemáticamente). – pdeschen

9

¿Por qué no utilizar la herencia? Puede anular sus enlaces específicos en el método overrideMe, dejando implementaciones compartidas en el método configure.

public class DevModule implements Module { 
    public void configure(Binder binder) { 
     binder.bind(InterfaceA.class).to(TestDevImplA.class); 
     overrideMe(binder); 
    } 

    protected void overrideMe(Binder binder){ 
     binder.bind(InterfaceC.class).to(ConcreteC.class); 
    } 
}; 

public class TestModule extends DevModule { 
    @Override 
    public void overrideMe(Binder binder) { 
     binder.bind(InterfaceC.class).to(MockC.class); 
    } 
} 

Y finalmente crear su inyector de esta manera:

Guice.createInjector(new TestModule()); 
+1

El '@ Override' no parece funcionar. Especialmente si se hace en un método que '@ Provides' algo. –

2

Desea utilizar Juckito donde se puede declarar la configuración personalizada para cada clase de prueba.

@RunWith(JukitoRunner.class) 
class LogicTest { 
    public static class Module extends JukitoModule { 

     @Override 
     protected void configureTest() { 
      bind(InterfaceC.class).to(MockC.class); 
     } 
    } 

    @Inject 
    private InterfaceC logic; 

    @Test 
    public testLogicUsingMock() { 
     logic.foo(); 
    } 
} 
3

Si no desea cambiar su módulo de producción y si usted tiene una estructura de proyecto por defecto experto-como como

src/test/java/... 
src/main/java/... 

Sólo puede crear una nueva clase ConcreteC en el directorio de prueba usando el mismo paquete que para tu clase original. Guice vinculará InterfaceC a ConcreteC desde su directorio de prueba, mientras que todas las demás interfaces se vincularán a sus clases de producción.

1

En una configuración diferente, tenemos más de una actividad definida en módulos separados. La actividad en la que se está inyectando está en un Módulo de biblioteca de Android, con su propia definición de módulo RoboGuice en el archivo AndroidManifest.xml.

La configuración se ve así. En el módulo Biblioteca existen estas definiciones:

AndroidManifest.xml:

<application android:allowBackup="true"> 
    <activity android:name="com.example.SomeActivity/> 
    <meta-data 
     android:name="roboguice.modules" 
     android:value="com.example.MainModule" /> 
</application> 

Entonces hemos de ser inyectado un tipo:

interface Foo { } 

Algunos implementación predeterminada de Foo:

class FooThing implements Foo { } 

MainModule configura la implementación de FooThing para Foo:

public class MainModule extends AbstractModule { 
    @Override 
    protected void configure() { 
     bind(Foo.class).to(FooThing.class); 
    } 
} 

Y, por último, una actividad que consume Foo:

public class SomeActivity extends RoboActivity { 
    @Inject 
    private Foo foo; 
} 

En el consumo de módulo de aplicación de Android, nos gustaría utilizar SomeActivity pero, para propósitos de prueba, se inyecta nuestra propia Foo.

public class SomeOtherActivity extends Activity { 
    @Override 
    protected void onResume() { 
     super.onResume(); 

     Intent intent = new Intent(this, SomeActivity.class); 
     startActivity(intent); 
    } 
} 

Uno podría argumentar para exponer el manejo de la aplicación cliente de módulo, sin embargo, tenemos que ocultar su mayoría los componentes que se inyectan porque el módulo Biblioteca es un SDK, y la exposición de piezas tiene implicaciones más grandes.

(Recuerde, esto es para pruebas, por lo que conocemos las partes internas de SomeActivity, y sabemos que consume un (paquete visible) Foo).

La forma en que encontré que funciona tiene sentido; utilizar el aumento al presupuesto sugerido para las pruebas :

public class SomeOtherActivity extends Activity { 
    private class OverrideModule 
      extends AbstractModule { 

     @Override 
     protected void configure() { 
      bind(Foo.class).to(OtherFooThing.class); 
     } 
    } 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_main); 
     RoboGuice.overrideApplicationInjector(
       getApplication(), 
       RoboGuice.newDefaultRoboModule(getApplication()), 
       Modules 
         .override(new MainModule()) 
         .with(new OverrideModule())); 
    } 

    @Override 
    protected void onResume() { 
     super.onResume(); 

     Intent intent = new Intent(this, SomeActivity.class); 
     startActivity(intent); 
    } 
} 

Ahora, cuando se inicia SomeActivity, se pondrá OtherFooThing por su inyectada Foo ejemplo.

Es una situación muy específica en la que, en nuestro caso, se usó OtherFooThing internamente para grabar situaciones de prueba, mientras que FooThing se usó, por defecto, para todos los demás usos.

Tenga en cuenta, que son usando #newDefaultRoboModule en nuestras pruebas de unidades, y todo funciona a la perfección.