Для "обычного" Java-проекта, переопределяющего зависимости в модульных тестах с помощью mock/fake, легко. Вы должны просто создать свой Кинжал и передать его классу 'main', который будет управлять вашим приложением.
Для Android вещи не такие простые, и я долго искал достойный пример, но я не смог найти, поэтому мне пришлось создать свою собственную реализацию и я буду очень благодарен, что обратная связь - это правильный способ использования Dagger 2 или более простой/более элегантный способ переопределить зависимости.
Здесь объяснение (источник проекта можно найти в github):
Учитывая, что у нас есть простое приложение, которое использует Dagger 2 с одним компонентом кинжала с одним модулем, мы хотим создать тесты для Android, которые используют JUnit4, Mockito и Espresso:
В классе 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);
}
...
В приведенном выше коде:
Обычный запуск приложения идет через tough onCreate()
, который вызывает initInjector()
, который создает инжектор, а затем вызывает onInjectorInitialized()
.
Метод externalInjectorInitialization()
может быть вызван модульными тестами для set
инжектора из внешнего источника, то есть a unit test.
До сих пор так хорошо.
Посмотрите, как выглядят вещи на стороне модулей:
Нам нужно создать вызовы MyTestApp, которые расширяют класс MyApp и переопределяют initInjector
пустым методом, чтобы избежать создания двойного инжектора (потому что мы создадим новый в нашем unit test):
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'
...
}
Когда выполняется unit test, наш оригинальный MyApp
заменяется на MyTestApp
. Теперь нам нужно создать и предоставить нашему компоненту/инжектору mocks/fakes в приложение с помощью 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)));
}
Вышеуказанный метод переопределения (модуля) работает, но для каждого теста требуется создать один тестовый класс, чтобы иметь возможность предоставлять отдельное правило/(настройка макетов) для каждого теста. Я подозреваю/думаю/надеюсь, что есть более простой и элегантный способ. Есть?
Этот метод во многом основан на ответе @tomrozb на на этот вопрос. Я просто добавил логику, чтобы избежать создания двойного инжектора.