我刚刚开始使用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映射时,它可以很好地工作。 / p>
此外,在与同事讨论这个问题后,我们似乎会超越整个模块并确保正确定义模块。这似乎可能会导致问题,虽然绑定在模块中放错位置并需要移动,因此可能会破坏大量测试,因为绑定可能不再可用于覆盖。
答案 0 :(得分:134)
这可能不是您正在寻找的答案,但如果您正在编写单元测试,您可能不应该使用注入器而是手动注入模拟或伪造对象。
另一方面,如果您真的想要替换单个绑定,可以使用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
的绑定移动到单独的模块来实现此目的。
答案 1 :(得分:9)
为什么不使用继承?您可以在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());
答案 2 :(得分:3)
如果您不想更改生产模块,并且您有类似
的默认maven项目结构src/test/java/...
src/main/java/...
您可以使用与原始类相同的包在测试目录中创建新类ConcreteC
。然后,Guice会将InterfaceC
绑定到您的测试目录中的ConcreteC
,而所有其他接口将绑定到您的生产类。
答案 3 :(得分:2)
您希望使用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();
}
}
答案 4 :(得分:1)
在不同的设置中,我们在单独的模块中定义了多个活动。正在注入的活动位于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的活动:
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
,并且它可以完美运行。