154

我刚刚开始使用 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 映射时效果很好接口和实现。

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

4

5 回答 5

164

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

另一方面,如果你真的想替换单个绑定,你可以使用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到单独的模块来完成此操作。

于 2009-02-10T05:04:02.503 回答
12

为什么不使用继承?您可以在方法中覆盖您的特定绑定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());
于 2014-08-27T13:19:13.253 回答
5

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

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

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

于 2015-01-06T16:34:16.113 回答
1

您想使用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();
    }
}
于 2014-11-11T11:35:03.440 回答
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它,它完美无缺。

于 2015-04-27T17:10:20.177 回答