web-dev-qa-db-ger.com

Überschreibende Bindung in Guice

Ich habe gerade angefangen, mit Guice zu spielen, und ein Anwendungsfall, den ich mir vorstellen kann, ist, dass ich in einem Test nur eine einzelne Bindung überschreiben möchte. Ich denke, ich möchte den Rest der Bindungen auf Produktionsebene verwenden, um sicherzustellen, dass alles korrekt eingerichtet ist und um Doppelungen zu vermeiden.

Stellen Sie sich also vor, ich habe das folgende Modul

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

Und in meinem Test möchte ich nur InterfaceC überschreiben, während ich InterfaceA und InterfaceB im Takt halte, also möchte ich etwas wie:

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

Ich habe auch Folgendes versucht, ohne Glück:

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

Weiß jemand, ob es möglich ist, das zu tun, was ich will, oder bellte ich den falschen Baum an?

--- Follow-up: Es scheint, dass ich erreichen kann, was ich will, wenn ich das @ImplementedBy-Tag auf der Schnittstelle verwende und dann im Testfall nur eine Bindung bereitstelle, die gut funktioniert, wenn es eine 1-1-Zuordnung zwischen gibt die Schnittstelle und Implementierung.

Nachdem wir dies mit einem Kollegen besprochen haben, scheinen wir uns auf den Weg gemacht zu haben, ein gesamtes Modul zu überschreiben und sicherzustellen, dass unsere Module korrekt definiert sind. Dies scheint jedoch ein Problem zu sein, wenn eine Bindung in einem Modul falsch platziert ist und verschoben werden muss, wodurch möglicherweise eine Last von Tests unterbrochen wird, da möglicherweise keine Bindungen mehr zum Überschreiben verfügbar sind.

127
tddmonkey

Dies ist möglicherweise nicht die Antwort, die Sie suchen. Wenn Sie jedoch Komponententests schreiben, sollten Sie wahrscheinlich keinen Injektor verwenden und falsche oder gefälschte Objekte lieber von Hand injizieren.

Wenn Sie jedoch eine einzelne Bindung wirklich ersetzen möchten, können Sie Modules.override(..) verwenden:

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

Siehe Details hier .

Aber wie in javadoc für Modules.overrides(..) empfohlen, sollten Sie Ihre Module so gestalten, dass Sie Bindungen nicht überschreiben müssen. In dem Beispiel, das Sie angegeben haben, können Sie dies erreichen, indem Sie die Bindung von InterfaceC in ein separates Modul verschieben.

137
albertb

Warum nicht Vererbung verwenden? Sie können Ihre spezifischen Bindungen in der overrideMe -Methode überschreiben und gemeinsam genutzte Implementierungen in der configure -Methode belassen.

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

Und zum Schluss erstellen Sie Ihren Injektor so:

Guice.createInjector(new TestModule());
9
Mon Calamari

Wenn Sie Ihr Produktionsmodul nicht ändern möchten und wenn Sie eine standardmäßige maven-ähnliche Projektstruktur wie haben

src/test/Java/...
src/main/Java/...

Sie können einfach eine neue Klasse ConcreteC in Ihrem Testverzeichnis erstellen, indem Sie dasselbe Paket wie für Ihre ursprüngliche Klasse verwenden. Guice bindet dann InterfaceC an ConcreteC aus Ihrem Testverzeichnis, während alle anderen Schnittstellen an Ihre Produktionsklassen gebunden werden.

3
Jan Gassen

Sie möchten Juckito verwenden, um Ihre benutzerdefinierte Konfiguration für jede Testklasse zu deklarieren.

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

In einem anderen Setup haben wir mehr als eine Aktivität in separaten Modulen definiert. Die Aktivität, in die injiziert wird, befindet sich in einem Android Library Module mit einer eigenen RoboGuice-Moduldefinition in der AndroidManifest.xml-Datei.

Das Setup sieht so aus. Im Bibliotheksmodul gibt es folgende Definitionen:

AndroidManifest.xml:

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

Dann haben wir einen Typ, der injiziert wird:

interface Foo { }

Einige Standardimplementierungen von Foo:

class FooThing implements Foo { }

MainModule konfiguriert die FooThing-Implementierung für Foo:

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

Und schließlich eine Aktivität, die Foo verbraucht:

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

Im konsumierenden Android Application Module) möchten wir SomeActivity verwenden, aber zu Testzwecken unser eigenes Foo einfügen.

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

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

Man könnte argumentieren, dass die Handhabung des Moduls der Client-Anwendung zugänglich gemacht werden soll. Wir müssen jedoch die zu injizierenden Komponenten größtenteils ausblenden, da das Bibliotheksmodul ein SDK ist und das Offenlegen von Teilen größere Auswirkungen hat.

(Denken Sie daran, dass dies zu Testzwecken dient, damit wir die Interna von SomeActivity kennen und wissen, dass es ein (sichtbares) Foo verbraucht.).

Die Art und Weise, wie ich fand, dass das funktioniert, macht Sinn; benutze den vorgeschlagenen Override für testing:

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

Wenn nun SomeActivity gestartet wird, erhält es OtherFooThing für seine injizierte Foo -Instanz.

Es ist eine sehr spezielle Situation, in der in unserem Fall OtherFooThing intern zum Aufzeichnen von Testsituationen verwendet wurde, während FooThing standardmäßig für alle anderen Verwendungszwecke verwendet wurde.

Denken Sie daran, wir sind mit #newDefaultRoboModule in unseren Unit-Tests, und es funktioniert einwandfrei.

1
Dave T.