Locked History Actions

guice/OverrideBinding

バインディングをオーバライドする方法

以下の元ネタは、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も参照のこと。