Esta é a maneira correta de usar o aplicativo Dagger 2 para Android no teste de unidade para substituir dependências por zombarias / falsificações?

Para um projeto Java 'regular', é fácil substituir as dependências nos testes de unidade por testes simulados / falsos. Você tem que simplesmente construir suaPunhal componente e entregue-o à classe 'main' que direciona seu aplicativo.

Para Android coisas sãonão é tão simples e procurei por um longo tempo um exemplo decente, mas não consegui encontrar, então tive que criar minha própria implementação e realmente aprecio o feedback. Esta é a maneira correta de usar o Dagger 2 ou há uma maneira mais simples / elegante substituir as dependências.

Aqui a explicação (a fonte do projeto pode ser encontrada no github):

Como temos um aplicativo simples que usa o Dagger 2 com um único componente do punhal com um único módulo, queremos criar testes de unidade Android que usam o JUnit4,Mockito eEspresso:

NoMyApp Application classe, o componente / injetor é inicializado assim:

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);
    }

    ...

No código acima: O início normal do aplicativo ocorreonCreate() que chamainitInjector() que cria o injetor e depois chamaonInjectorInitialized().

oexternalInjectorInitialization() método deve ser chamado pelos testes de unidade paraset o injetor de fonte externa, isto é, um teste de unidade.

Por enquanto, tudo bem.

Vamos ver como ficam as coisas no lado dos testes de unidade:

Precisamos criar chamadas MyTestApp que estendem a classe MyApp e substitueminitInjector com o método vazio para evitar a criação de injetores duplos (porque criaremos um novo em nosso teste de unidade):

public class MyTestApp extends MyApp {
    @Override
    protected void initInjector() {
        // empty
    }
}

Em seguida, precisamos substituir o MyApp original pelo MyTestApp. Isso é feito através do executor de teste personalizado:

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);
    }
}

... ondenewApplication() substituímos efetivamente a classe de aplicativo original pela de teste.

Então, temos que dizer à estrutura de teste que possuímos e queremos usar nosso executor de teste personalizado, portanto, no build.gradle, adicionamos:

defaultConfig {
    ...
    testInstrumentationRunner 'com.bolyartech.d2overrides.utils.MyTestRunner'
    ...
}

Quando um teste de unidade é executado, nossoMyApp é substituído porMyTestApp. Agora temos que criar e fornecer ao nosso componente / injetor zombarias / falsificações para o aplicativo comexternalInjectorInitialization(). Para esse fim, estendemos o ActivityTestRule normal:

@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);

    }
};

e depois fazemos o teste da maneira usual:

@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)));
}

O método acima para substituições (módulo) funciona, mas exige a criação de uma classe de teste para cada teste, a fim de poder fornecer uma regra / (configuração de zombaria) separada para cada teste. Suspeito / acho / espero que exista uma maneira mais fácil e elegante. Existe?

Esse método é amplamente baseado na resposta de @tomrozb paraessa questão. Acabei de adicionar a lógica para evitar a criação de injetores duplos.

questionAnswers(2)

yourAnswerToTheQuestion