在Guice中覆盖绑定


138

我刚刚开始玩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
BorisPavlović09年

Answers:


149

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

另一方面,如果您确实要替换单个绑定,则可以使用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所Modules.overrides(..)建议的那样,您应该以无需覆盖绑定的方式设计模块。在您给出的示例中,可以通过将的绑定移动InterfaceC到单独的模块来完成此操作。


9
谢谢阿尔伯特,这使我朝着自己想要的方向前进。那是在生产版本中,但是!这是用于集成测试,而不是单元测试,这就是我为什么要确保所有其他东西都正确构建的原因
tddmonkey

1
我在代码中添加了一个具体示例。它能使您走得更远吗?
albertb

1
除非我弄错了,否则在这样做时会ovveride放开适当的内容Stage(即系统地使用开发)。
pdeschen 2014年

4
大小事项。当依赖图增长时,用手进行接线可能会很痛苦。同样,在更改接线时,您需要手动更新所有手动接线位置。通过覆盖,您可以自动进行处理。
yoosiba

3
@pdeschen即在吉斯3的错误即我固定为吉斯4.
Tavian巴恩斯

9

为什么不使用继承?您可以在overrideMemethod中覆盖特定的绑定,而将共享的实现保留在configuremethod中。

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

3
@Override似乎没有工作时。特别是如果在某种方法上完成的@Provides话。
Sasanka Panguluri 2014年

4

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

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

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


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

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

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

请记住,我们正在#newDefaultRoboModule单元测试使用它,它可以完美地工作。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.