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

Ответы на вопрос(2)

Ваш ответ на вопрос