【问题标题】:Overriding Binding in Guice覆盖 Guice 中的绑定
【发布时间】:2009-01-27 11:42:11
【问题描述】:

我刚刚开始使用 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);
    }
}

在我的测试中,我只想覆盖 InterfaceC,同时保持 InterfaceA 和 InterfaceB 的完整性,所以我想要类似的东西:

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 映射时,它可以很好地工作。

此外,在与同事讨论此问题后,我们似乎会走上覆盖整个模块并确保正确定义模块的道路。尽管绑定在模块中放错位置并且需要移动,但这似乎可能会导致问题,因此可能会破坏大量测试,因为绑定可能不再可用于覆盖。

【问题讨论】:

  • 就像“找错树”这句话:D

标签: java unit-testing guice


【解决方案1】:

这可能不是您正在寻找的答案,但如果您正在编写单元测试,您可能不应该使用注入器,而应该手动注入模拟或假对象。

另一方面,如果你真的想替换单个绑定,你可以使用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()));

查看详情here

但是正如Modules.overrides(..) 的javadoc 所建议的那样,您应该以不需要覆盖绑定的方式设计您的模块。在您给出的示例中,您可以通过将 InterfaceC 的绑定移动到单独的模块来完成此操作。

【讨论】:

  • 感谢阿尔伯特,这让我在做我想做的事情的道路上有所收获。那是在生产版本中呢!这是用于集成测试,而不是单元测试,这就是为什么我要确保其他所有内容都正确构建
  • 我在代码中添加了一个具体示例。它能让你走得更远吗?
  • 除非我弄错了,否则ovveride 在这样做时会丢失正确的Stage(即系统地使用开发)。
  • 尺寸很重要。当您的依赖关系图增长时,手动连接可能会很痛苦。此外,当接线更改时,您需要手动更新所有手动接线位置。覆盖允许您自动处理。
  • @pdeschen 这是 Guice 3 中的一个错误,I fixed 用于 Guice 4。
【解决方案2】:

为什么不使用继承?您可以在 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());

【讨论】:

  • @Override 似乎不起作用。特别是如果它是在 @Provides 某事的方法上完成的。
【解决方案3】:

如果您不想更改您的生产模块,并且如果您有一个默认的类似 maven 的项目结构,例如

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

您可以使用与原始类相同的包在您的测试目录中创建一个新类ConcreteC。然后,Guice 将从您的测试目录将InterfaceC 绑定到ConcreteC,而所有其他接口将绑定到您的生产类。

【讨论】:

    【解决方案4】:

    您想使用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();
        }
    }
    

    【讨论】:

      【解决方案5】:

      在不同的设置中,我们在不同的模块中定义了多个活动。注入的活动位于 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 的 Activity:

      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 启动时,它会为注入的Foo 实例获取OtherFooThing

      这是一种非常特殊的情况,在我们的例子中,OtherFooThing 用于内部记录测试情况,而 FooThing 默认用于所有其他用途。

      请记住,我们正在在我们的单元测试中使用#newDefaultRoboModule,它可以完美运行。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-04-09
        • 2017-09-28
        • 1970-01-01
        • 1970-01-01
        • 2014-09-15
        • 2017-03-25
        相关资源
        最近更新 更多