Guiceでのバインディングのオーバーライド


138

私は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);

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

---フォローアップ:インターフェイスで@ImplementedByタグを使用して、テストケースでバインディングを提供するだけで、望みどおりの結果が得られるようです。これは、1-1のマッピングがある場合にうまく機能します。インターフェースと実装。

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


7
「間違った木を吠える」というフレーズのように:D
ボリス・パブロビッチ

回答:


149

これはあなたが探している答えではないかもしれませんが、単体テストを作成している場合は、おそらくインジェクターを使用してはならず、手動でモックまたは偽のオブジェクトをインジェクトする必要があります。

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

詳細はこちら

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


9
アルバート、ありがとう それはまだプロダクションリリースです!そして、これは統合テストではなく、私は他のすべてが正しく構築されていることを確認したい理由のユニットテストのためのものです
tddmonkey

1
具体的な例をコードに追加しました。それはあなたをさらに前進させますか?
albertb 2009

1
私が間違えない限り、そうしている間ovverideは適切なものStageを失います(つまり、DEVELOPMENTは体系的に使用されます)。
pdeschen 2014年

4
サイズが重要です。ディペンデンシーグラフが大きくなると、手作業で接続するのはかなり大変です。また、配線を変更する場合は、手動で行ったすべての配線場所を手動で更新する必要があります。オーバーライドすると、それを自動的に処理できます。
yoosiba 14

3
ことGuiceの3のバグである@pdeschen Iが固定 Guiceの4の
Tavianバーンズ

9

継承を使用しないのはなぜですか?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());

3
@Override動作するようには思えません。特にそれが@Provides何かの方法で行われた場合。
Sasanka Panguluri 2014

4

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

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

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


2

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

1

別の設定では、複数のアクティビティが別々のモジュールで定義されています。注入されるアクティビティはAndroidライブラリモジュールにあり、AndroidManifest.xmlファイルに独自のRoboGuiceモジュール定義があります。

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

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アプリケーションモジュールでは、使用したいのです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を消費していることを思い出してください)。

それが機能することを私が見つけた方法は理にかなっています。テストのために提案されたオーバーライドを使用します

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はデフォルトで他のすべての用途に使用されました。

覚えておいて、私たちはしている使用して#newDefaultRoboModule私たちのユニットテストでは、それが完璧に動作します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.