Переопределение привязки в Guice

141

Я только начал играть с 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 в интерфейсе, а затем просто предоставлю привязку в тестовом примере, который отлично работает, когда между интерфейс и реализация.

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

tddmonkey
источник
7
Как фраза «лает не на то дерево»: D
Борис Павлович

Ответы:

151

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

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

Подробности смотрите здесь .

Но, как Modules.overrides(..)рекомендует javadoc for , вы должны проектировать свои модули таким образом, чтобы вам не приходилось переопределять привязки. В приведенном вами примере это можно сделать, переместив привязку InterfaceCв отдельный модуль.

Альберт
источник
9
Спасибо, Альберт, это помогает мне делать то, что я хочу. Это пока что в производственном выпуске! И это для интеграционных тестов, а не для модульных тестов, поэтому я хочу убедиться, что все остальное строится правильно
tddmonkey
1
Я добавил в код конкретный пример. Это поможет вам продвинуться дальше?
albertb
1
Если не ошибаюсь, при этом ovverideтеряет должное Stage(т.е. систематически используется РАЗРАБОТКА).
pdeschen
4
Размер имеет значение. Когда ваш граф зависимостей растет, подключение вручную может быть довольно сложной задачей. Также при изменении проводки вам необходимо вручную обновить все места ручной проводки. Переопределение позволяет вам справиться с этим автоматически.
yoosiba
3
@pdeschen Это ошибка в Guice 3, которую я исправил для Guice 4.
Тавиан Барнс
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());
Мон-каламари
источник
3
@OverrideНе похоже на работу. Особенно если это делается по методу чего- @Providesто.
Сасанка Пангулури
4

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

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

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

Ян Гассен
источник
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();
    }
}
Esukram
источник
1

В другой настройке у нас есть несколько действий, определенных в отдельных модулях. Внедренная активность находится в модуле библиотеки 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);
    }
}

И, наконец, Activity, потребляющее 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наших модульных тестов, и она работает безупречно.

Дэйв Т.
источник