web-dev-qa-db-ja.com

Guiceでバインディングをオーバーライドする

私はGuiceで遊んだばかりで、考えられるユースケースは、テストで単一のバインディングをオーバーライドしたいということです。すべてのプロダクションレベルのバインディングを使用して、すべてが正しくセットアップされ、重複を回避したいと思います。

次のモジュールがあると想像してください

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

そして、私のテストでは、InterfaceAとInterfaceBをそのままにして、InterfaceCのみをオーバーライドしたいので、次のようなものが必要です。

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

私は次のことも試しましたが、運はありません:

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

誰かが私がやりたいことができるのか、間違った木を完全にcompletelyえているのかを知っていますか?

---フォローアップ:インターフェイスで@ImplementedByタグを使用し、テストケースでバインディングを提供するだけで、目的を達成できるように見えます。インターフェースと実装。

また、同僚とこれについて議論した後、モジュール全体をオーバーライドし、モジュールを正しく定義することを確実にする道を進んでいくように思われます。これは、バインディングがモジュール内で誤って配置され、移動する必要がある場合に問題を引き起こす可能性があるように見えます。

127
tddmonkey

これはあなたが探している答えではないかもしれませんが、ユニットテストを書いているなら、おそらくインジェクターを使うべきではなく、モックや偽のオブジェクトを手で注入するべきです。

一方、単一のバインディングを本当に置き換えたい場合は、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()));
_

詳細を参照してください こちら

ただし、Modules.overrides(..)のjavadocが推奨しているように、バインディングをオーバーライドする必要がないようにモジュールを設計する必要があります。あなたが与えた例では、InterfaceCのバインディングを別のモジュールに移動することでそれを達成できます。

137
albertb

なぜ継承を使用しないのですか? overrideMeメソッドで特定のバインディングをオーバーライドし、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);
    }
}

最後に、この方法でインジェクターを作成します。

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

プロダクションモジュールを変更したくない場合、およびデフォルトのMavenのようなプロジェクト構造がある場合

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

元のクラスと同じパッケージを使用して、テストディレクトリに新しいクラスConcreteCを作成するだけです。 GuiceはテストディレクトリからInterfaceCConcreteCにバインドしますが、他のすべてのインターフェイスは実稼働クラスにバインドされます。

3
Jan Gassen

Juckito を使用すると、各テストクラスのカスタム構成を宣言できます。

@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

別のセットアップでは、別々のモジュールで複数のアクティビティが定義されています。挿入されるアクティビティは、AndroidManifest.xmlファイルに独自のRoboGuiceモジュール定義を持つAndroid Library Moduleにあります。

セットアップは次のようになります。ライブラリモジュールには、次の定義があります。

AndroidManifest.xml:

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

次に、注入される型があります。

interface Foo { }

Fooのデフォルト実装:

class FooThing implements Foo { }

MainModuleは、FooのFooThing実装を構成します。

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

そして最後に、Fooを消費するアクティビティ:

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

使用するAndroid Application Module)では、SomeActivityを使用しますが、テスト目的で、独自のFooを挿入します。

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

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

モジュール処理をクライアントアプリケーションに公開することを主張するかもしれませんが、ライブラリモジュールはSDKであるため、注入されるコンポーネントをほとんど隠す必要があり、作品の公開には大きな意味があります。

(これはテスト用であるため、SomeActivityの内部を知っており、Fooを消費することがわかります)。

私が見つけた方法は理にかなっています。 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);
    }
}

これで、SomeActivityが開始されると、インジェクトされたOtherFooThingインスタンスに対してFooを取得します。

私たちの場合、テスト状況を記録するためにOtherFooThingが内部的に使用され、FooThingは他のすべての用途にデフォルトで使用された非常に特殊な状況です。

areを使用して#newDefaultRoboModule単体テストでは、問題なく動作します。

1
Dave T.