Sostituzione di Binding in Guice


138

Ho appena iniziato a giocare con Guice e un caso d'uso a cui riesco a pensare è che in un test voglio solo scavalcare un singolo binding. Penso che mi piacerebbe utilizzare il resto dei collegamenti a livello di produzione per garantire che tutto sia impostato correttamente ed evitare duplicazioni.

Quindi immagina di avere il seguente modulo

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

E nel mio test voglio solo sostituire InterfaceC, mantenendo intatta InterfaceA e InterfaceB, quindi vorrei qualcosa del tipo:

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

Ho anche provato quanto segue, senza fortuna:

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

Qualcuno sa se è possibile fare quello che voglio o sto abbaiando completamente l'albero sbagliato ??

--- Follow-up: Sembrerebbe di poter ottenere ciò che voglio se utilizzo il tag @ImplementedBy sull'interfaccia e quindi fornisco un binding nel caso di test, che funziona bene quando c'è un mapping 1-1 tra l'interfaccia e l'implementazione.

Inoltre, dopo aver discusso di questo con un collega, sembrerebbe che avremmo intrapreso la strada per scavalcare un intero modulo e assicurarci che i nostri moduli siano definiti correttamente. Questo sembra che potrebbe causare un problema anche se un'associazione è posizionata in modo errato in un modulo e deve essere spostata, quindi è possibile interrompere un carico di test poiché le associazioni potrebbero non essere più disponibili per essere sostituite.


7
Come la frase "abbaiare sull'albero sbagliato": D
Boris Pavlović,

Risposte:


149

Questa potrebbe non essere la risposta che stai cercando, ma se stai scrivendo test unitari, probabilmente non dovresti usare un iniettore e piuttosto iniettare oggetti falsi o falsi a mano.

D'altra parte, se si desidera veramente sostituire una singola associazione, è possibile utilizzare 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()));

Vedi i dettagli qui .

Ma come Modules.overrides(..)consiglia javadoc for , dovresti progettare i tuoi moduli in modo tale da non dover scavalcare i binding. Nell'esempio che hai fornito, puoi farlo spostando il binding di InterfaceCun modulo separato.


9
Grazie Albert, questo mi porta in qualche modo lungo la strada per fare ciò che voglio. Questo è in una versione di produzione eppure! E questo è per i test di integrazione, non per i test unitari, motivo per cui voglio assicurarmi che tutto il resto venga costruito correttamente
tddmonkey,

1
Ho aggiunto un esempio concreto al codice. Ti porta oltre?
albertb,

1
A meno che non mi sbagli, ovverideperde il giusto Stagementre lo fa (cioè SVILUPPO viene sistematicamente usato).
pdeschen,

4
Le misure contano. Quando il grafico delle dipendenze cresce, il cablaggio a mano può essere piuttosto doloroso. Inoltre, quando si modificano i cablaggi, è necessario aggiornare manualmente tutti i punti di cablaggio manuali. L'override ti consente di gestirlo automaticamente.
Yoosiba,

3
@pdeschen Questo è un bug di Guice 3 che ho corretto per Guice 4.
Tavian Barnes,

9

Perché non usare l'eredità? È possibile ignorare i collegamenti specifici nel overrideMemetodo, lasciando implementazioni condivise nel configuremetodo.

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

E infine crea il tuo iniettore in questo modo:

Guice.createInjector(new TestModule());

3
Il @Overridenon sembra al lavoro. Soprattutto se è fatto su un metodo che @Providesqualcosa.
Sasanka Panguluri,

4

Se non si desidera modificare il modulo di produzione e se si dispone di una struttura di progetto simile a quella predefinita come

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

Puoi semplicemente creare una nuova classe ConcreteCnella tua directory di test usando lo stesso pacchetto della tua classe originale. Guice si collegherà quindi InterfaceCalla ConcreteCdirectory di test mentre tutte le altre interfacce saranno associate alle classi di produzione.


2

Vuoi usare Juckito dove puoi dichiarare la tua configurazione personalizzata per ogni classe di test.

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

1

In una configurazione diversa, abbiamo più di un'attività definita in moduli separati. L'attività che viene iniettata è in un Modulo libreria Android, con la propria definizione del modulo RoboGuice nel file AndroidManifest.xml.

L'installazione è simile a questa. Nel modulo Libreria ci sono queste definizioni:

AndroidManifest.xml:

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

Quindi viene iniettato un tipo:

interface Foo { }

Alcune implementazioni predefinite di Foo:

class FooThing implements Foo { }

MainModule configura l'implementazione di FooThing per Foo:

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

E infine, un'attività che consuma Foo:

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

Nel consumo del Modulo applicativo Android, vorremmo utilizzare SomeActivityma, a scopo di test, iniettare il nostro Foo.

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

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

Si potrebbe obiettare di esporre la gestione del modulo all'applicazione client, tuttavia, è necessario nascondere principalmente i componenti che vengono iniettati perché il Modulo libreria è un SDK e l'esposizione dei pezzi ha implicazioni maggiori.

(Ricorda, questo è per i test, quindi conosciamo gli interni di SomeActivity e sappiamo che consuma un Foo (pacchetto visibile)).

Il modo in cui ho scoperto che funziona ha un senso; utilizzare l'override suggerito per il test :

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

Ora, quando SomeActivityviene avviato, otterrà la OtherFooThingsua Fooistanza iniettata .

È una situazione molto specifica in cui, nel nostro caso, OtherFooThing è stato utilizzato internamente per registrare situazioni di test, mentre FooThing è stato utilizzato, per impostazione predefinita, per tutti gli altri usi.

Tieni presente che stiamo utilizzando i #newDefaultRoboModulenostri test unitari e funziona perfettamente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.