Переопределение связывания в Guice - PullRequest
126 голосов
/ 27 января 2009

Я только начал играть с 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.

Кроме того, после обсуждения этого с коллегой может показаться, что мы пошли по пути переопределения всего модуля и обеспечения правильного определения наших модулей. Похоже, что это может вызвать проблему, хотя привязка в модуле неуместна и требует перемещения, что может привести к прерыванию загрузки тестов, поскольку привязки могут быть недоступны для переопределения.

Ответы [ 5 ]

134 голосов
/ 10 февраля 2009

Возможно, это не тот ответ, который вы ищете, но если вы пишете модульные тесты, вам, вероятно, не следует использовать инжектор, а скорее вводить фиктивные или поддельные объекты вручную.

С другой стороны, если вы действительно хотите заменить одну привязку, вы можете использовать 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 голосов
/ 27 августа 2014

Почему бы не использовать наследование? Вы можете переопределить ваши конкретные привязки в методе 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());
3 голосов
/ 06 января 2015

Если вы не хотите менять производственный модуль и если у вас есть структура проекта по умолчанию, похожая на maven, например

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

Вы можете просто создать новый класс ConcreteC в своей тестовой директории, используя тот же пакет, что и для исходного класса. Затем Guice свяжет InterfaceC с ConcreteC из вашего тестового каталога, тогда как все остальные интерфейсы будут привязаны к вашим рабочим классам.

2 голосов
/ 11 ноября 2014

Вы хотите использовать 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 голос
/ 27 апреля 2015

В другой настройке у нас есть несколько действий, определенных в отдельных модулях. Внедряемое действие находится в модуле библиотеки Android с собственным определением модуля RoboGuice в файле AndroidManifest.xml.

Настройка выглядит следующим образом. В библиотечном модуле есть следующие определения:

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 настраивает реализацию FooThing для Foo:

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 в наших модульных тестах, и это работает безупречно.

...