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.