Переопределение привязки в 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);
}
}
И в моем тесте я только хочу переопределить интерфейс, сохраняя 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 между интерфейсом и реализацией.
Кроме того, обсудив это с коллегой, казалось бы, мы отправимся в путь переопределения всего модуля и обеспечения правильности определения наших модулей. Похоже, что это может вызвать проблему, хотя привязка привязана к модулю и должна быть перемещена, что может привести к поломке нагрузок тестов, поскольку привязки могут перестать быть доступными для переопределения.
Ответы
Ответ 1
Это может быть не тот ответ, который вы ищете, но если вы пишете модульные тесты, вы, вероятно, не должны использовать инжектор, а скорее будете вводить макет или поддельные объекты вручную.
С другой стороны, если вы действительно хотите заменить одно привязку, вы можете использовать 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
к отдельному модулю.
Ответ 2
Почему бы не использовать наследование? Вы можете переопределить свои привязки в методе 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
Если вы не хотите изменять свой производственный модуль и если у вас есть структура проекта, подобная maven, например
src/test/java/...
src/main/java/...
Вы можете просто создать новый класс ConcreteC
в тестовом каталоге, используя тот же пакет, что и для вашего исходного класса. Затем Guice свяжет InterfaceC
с ConcreteC
с вашим тестовым каталогом, тогда как все остальные интерфейсы будут привязаны к вашим производственным классам.
Ответ 4
Вы хотите использовать 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();
}
}
Ответ 5
В другой настройке мы имеем несколько операций, определенных в отдельных модулях. Занятие, которое вводится в модуль библиотеки 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
в наших модульных тестах, и он работает безупречно.