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

另外,在与同事讨论过这个问题之后,我们似乎要重写整个模块并确保正确定义模块。这似乎会导致一个问题,虽然绑定在模块中放错了位置,需要移动,因此可能会破坏测试负载,因为绑定可能不再可用以被覆盖。

81840 次浏览

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

另一方面,如果您确实希望替换单个绑定,则可以使用 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()));

详情请参阅 模块文档

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

为什么不使用继承? 可以在 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 然后将 InterfaceC从您的测试目录绑定到 ConcreteC,而所有其他接口将绑定到您的生产类。

在不同的设置中,我们在单独的模块中定义了多个活动。被注入的活动位于 Android Library Module 中,在 AndroidManifest.xml 文件中有自己的 RoboGuice 模块定义。

设置看起来像这样。在库模块中有这些定义:

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);
}
}

有人可能认为应该将模块处理暴露给客户机应用程序,但是,我们主要需要隐藏正在注入的组件,因为 Library Module 是一个 SDK,暴露组件具有更大的含义。

(请记住,这是用于测试的,因此我们知道 SomActivity 的内部结构,并且知道它使用一个(可见的包) 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,它的工作完美无缺。