バインディングをオーバライドする方法
以下の元ネタは、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も参照のこと。
