Это правильный способ использовать приложение Dagger 2 для Android в модульном тесте для переопределения зависимостей с помощью макетов / подделок?
Для «обычного» Java-проекта легко переопределить зависимости в модульных тестах с помощью mock / fake. Вы должны просто построить свойКинжал компонент и передать его «основному» классу, который управляет вашим приложением.
Для Android вещине так просто и я долго искал достойный пример, но мне не удалось найти, поэтому мне пришлось создать собственную реализацию, и я буду очень признателен за обратную связь - это правильный способ использования Dagger 2 или есть более простой / более элегантный способ переопределить зависимости.
Вот объяснение (источник проекта можно найти на github):
Учитывая, что у нас есть простое приложение, которое использует Dagger 2 с одним компонентом кинжала с одним модулем, мы хотим создать модульные тесты Android, которые используют JUnit4,Mockito а такжеЭспрессо:
вMyApp
Application
Класс компонент / инжектор инициализируется следующим образом:
public class MyApp extends Application {
private MyDaggerComponent mInjector;
public void onCreate() {
super.onCreate();
initInjector();
}
protected void initInjector() {
mInjector = DaggerMyDaggerComponent.builder().httpModule(new HttpModule(new OkHttpClient())).build();
onInjectorInitialized(mInjector);
}
private void onInjectorInitialized(MyDaggerComponent inj) {
inj.inject(this);
}
public void externalInjectorInitialization(MyDaggerComponent injector) {
mInjector = injector;
onInjectorInitialized(injector);
}
...
В приведенном выше коде: нормальный запуск приложения идет черезonCreate()
какие звонкиinitInjector()
который создает инжектор, а затем вызываетonInjectorInitialized()
.
externalInjectorInitialization()
метод должен вызываться модульными тестами, чтобыset
инжектор из внешнего источника, то есть модульный тест.
Все идет нормально.
Посмотрим, как обстоят дела на стороне юнит-тестов:
Нам нужно создать вызовы MyTestApp, которые расширяют класс MyApp и переопределяютinitInjector
с пустым методом, чтобы избежать создания двойного инжектора (потому что мы создадим новый в нашем модульном тесте):
public class MyTestApp extends MyApp {
@Override
protected void initInjector() {
// empty
}
}
Затем мы должны каким-то образом заменить оригинальный MyApp на MyTestApp. Это делается с помощью пользовательского бегуна:
public class MyTestRunner extends AndroidJUnitRunner {
@Override
public Application newApplication(ClassLoader cl,
String className,
Context context) throws InstantiationException,
IllegalAccessException,
ClassNotFoundException {
return super.newApplication(cl, MyTestApp.class.getName(), context);
}
}
... в которойnewApplication()
мы фактически заменим исходный класс приложения на тестовый.
Затем мы должны сообщить инфраструктуре тестирования, что у нас есть, и хотим использовать наш пользовательский тестовый прогон, поэтому в build.gradle мы добавим:
defaultConfig {
...
testInstrumentationRunner 'com.bolyartech.d2overrides.utils.MyTestRunner'
...
}
Когда юнит тест запускается наш оригинальныйMyApp
заменяется наMyTestApp
, Теперь мы должны создать и предоставить нашему компоненту / инжектору макеты / подделки для приложения сexternalInjectorInitialization()
, Для этого мы расширяем обычный ActivityTestRule:
@Rule
public ActivityTestRule<Act_Main> mActivityRule = new ActivityTestRule<Act_Main>(
Act_Main.class) {
@Override
protected void beforeActivityLaunched() {
super.beforeActivityLaunched();
OkHttpClient mockHttp = create mock OkHttpClient
MyDaggerComponent injector = DaggerMyDaggerComponent.
builder().httpModule(new HttpModule(mockHttp)).build();
MyApp app = (MyApp) InstrumentationRegistry.getInstrumentation().
getTargetContext().getApplicationContext();
app.externalInjectorInitialization(injector);
}
};
и тогда мы делаем наш тест обычным способом:
@Test
public void testHttpRequest() throws IOException {
onView(withId(R.id.btn_execute)).perform(click());
onView(withId(R.id.tv_result))
.check(matches(withText(EXPECTED_RESPONSE_BODY)));
}
Вышеупомянутый метод для (модуля) переопределений работает, но он требует создания одного класса теста для каждого теста, чтобы иметь возможность предоставить отдельное правило / (mocks setup) для каждого теста. Я подозреваю / думаю / надеюсь, что есть более простой и элегантный способ. Есть?
Этот метод в значительной степени основан на ответе @tomrozb дляэтот вопрос, Я просто добавил логику, чтобы избежать создания двойного инжектора.