バインディングをオーバライドする方法
以下の元ネタは、Overriding Bindings in Guice
ProductionModule
製品で使用するモジュールは以下であるとする。
public class ProductionModule extends AbstractModule { protected void configure() { bind(LoginService.class).to(RemoteLoginService.class); bind(DataStore.class).to(MySqlDatabase.class); bind(BillerConnection.class).to(SecureBillerConnection.class); bind(LoggingEngine.class).to(DistributedLoggingEngine.class); } }
テスト時には、上のモジュールの一部だけを差し替えてテストを行いたいとする。例えば、LoginServiceはテスト用のクラスFakeLoginServiceにバインドしたい。どうすればよいか?単純に以下のようにしてしまうとエラーになってしまう。
Injector injector = Guice.createInjector(new ProductionModule(), new AbstractModule() { protected void configure() { bind(LoginService.class).to(FakeLoginService.class); } } );
一つのLoginServiceに対して複数のバインドが存在するからである。
モジュールを分割する方法
バインディングごとにモジュールを分割する方法がベストであるとのこと。
public class ProductionLoginModule extends AbstractModule { protected void configure() { bind(LoginService.class).to(RemoteLoginService.class); } }
これらの複数のモジュールを取捨選択してGuice.createInjectorに指定すればよい。
継承を使う方法
ProductionModuleではLoginServiceのバインドする部分を一つのメソッドに追い出しておく。それを継承したTestModuleでは、そのメソッドをオーバライドする。
public class ProductionModule extends AbstractModule { protected void configure() { bindLoginService(); bind(DataStore.class).to(MySqlDatabase.class); bind(BillerConnection.class).to(SecureBillerConnection.class); bind(LoggingEngine.class).to(DistributedLoggingEngine.class); } protected void bindLoginService() { bind(LoginService.class).to(RemoteLoginService.class); } } public class TestModule extends ProductionModule { @Override protected void bindLoginService() { bind(LoginService.class).to(FakeLoginService.class); } }
Moduleをオーバライドする方法
※注意:おそらくOverriding indings in Guiceではベータ版の仕様で説明されているものと思われ、Guice2.0正規版では動作しない。正しくは以下のよう記述になる。
テスト用のモジュールを以下のように作成し、
public class OverridesModule extends AbstractModule { @Override protected void configure() { bind(LoginService.class).to(FakeLoginService.class); } }
元のModuleを以下のようにオーバライドして新しいModuleを作成し、Injectorを生成する。
Module newModule = Modules .override(new ProductionModule()) .with(new OverridesModule()); Injector injector = Guice.createInjector(newModule);
テスト用に一部モジュールを入れ替える
テスト時には常に製品用モジュールのバインディングの一部を差し替えて利用するとした場合、 テストユニットの中でいちいちModules.override()を呼び出すの面倒である。以下のようにしてみたらどうか。
import com.google.inject.*; import com.google.inject.util.*; public class TestInjector { public static Injector create(Module...overrideModules) { Module[]productionModules = new Module[] { new ProductionModule(), .... }; return Guice.createInjector( Modules.override(productionModules).with(overrideModules)); } }
これをテストユニットでは以下のように呼び出す。
Injector injector = TestInjector.create(new AbstractModule() { @Override protected void configure() { bind(LoginService.class).to(FakeLoginService.class); } });
さらにもしデフォルトでテスト用のオーバーライドモジュールがある場合は、以下のようにも書ける。 overridesは何段になっても構わないようである。
public class TestInjector { public static Injector create(Module...overrideModules) { Module[]productionModules = new Module[] { new ProductionModule(), .... }; Module[]testModules = new Module[] { new TestModule(), .... }; return Guice.createInjector( Modules.override( Modules.override(productionModules).with(testModules) ).with(overrideModules) ); } }
このようにしておき、さらに個々のテストユニットで好みのオーバーライドが可能だ。
Injector injector = TestInjector.create(new AbstractModule() { @Override protected void configure() { bind(LoginService.class).to(FakeLoginService.class); } });
おまけ~PriviteModuleの使い方
PrivateModuleを使うと、
- exposeしたバインディングだけが外部から使用でき、
- それ以外のバインディングは内部からのみ使用できる。
以下では、C.classに対するバインディングを複数作成しているが、CImplOneはAからのみ使用可能で、CImplTwoはBからのみ使用可能になる。
package test; import com.google.inject.*; public class Main { public interface A { } public static class AImpl implements A { @Inject C c; public String toString() { return c.getClass().toString(); } } public interface B { } public static class BImpl implements B { @Inject C c; public String toString() { return c.getClass().toString(); } } public interface C { } public static class CImplOne implements C { } public static class CImplTwo implements C{ } public static void main(String[]args) { Injector injector = Guice.createInjector( new PrivateModule(){ protected void configure() { binder().bind(A.class).to(AImpl.class); binder().bind(C.class).to(CImplOne.class); expose(A.class); } }, new PrivateModule(){ protected void configure() { binder().bind(B.class).to(BImpl.class); binder().bind(C.class).to(CImplTwo.class); expose(B.class); } } ); A a = injector.getInstance(A.class); B b = injector.getInstance(B.class); System.out.println(a.toString()); System.out.println(b.toString()); } }
実行結果は
class test.Main$CImplOne class test.Main$CImplTwo
Robot Leg Problemも参照のこと。