Guice中的覆盖绑定

问题描述:

我刚刚开始玩Guice,我可以想到的一个用例是,在测试中我只想覆盖单个绑定。我想我想使用剩余的生产级绑定来确保一切安装正确并避免重复。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接口与实现之间的映射。

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

+7

就像“咆哮错误的树”一样:D – 2009-01-27 14:34:48

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

在另一方面,如果你真的想更换一个单一的结合,你可以使用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

但作为javadoc Modules.overrides(..)建议,你应该设计你的模块,你不需要重写绑定。在您给出的示例中,您可以通过将InterfaceC的绑定移动到单独的模块来完成此操作。

+7

感谢艾伯特,这使我得以顺利完成自己想做的事。这是在生产版本,但寿!这是为了集成测试,而不是单元测试,我为什么要确保正确构建其他所有东西 – tddmonkey 2009-02-10 08:58:48

+1

我已经为代码添加了一个具体示例。它是否让你更进一步? – albertb 2009-02-11 02:42:13

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

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

如果你不想改变你的生产模块,如果你有一个默认的Maven样的项目结构像

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

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

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

最后,消耗符的活动:

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,它的工作完美无瑕。