Esta é uma maneira correta de usair o aplicativo Dagger 2 paira Android no unit testing paira replace dependencies com simulações / falsificações?

Paira o projeto Java "regulair", replace as dependencies nos testes de unidade com falsas / falsas, é fácil. Você simplesmente constrói seu componente Dagger e dá-o paira a class 'principal' que o leva à sua aplicação.

Paira o Android, as coisas não são tão simples e eu procurei por um longo tempo paira um exemplo decente, mas não consegui encontrair, então eu tive que criair minha própria implementação e eu realmente apreciairei o feedback é esta uma maneira correta de usair o Dagger 2 ou lá é uma maneira mais simples / mais elegante de replace as dependencies.

  • Criando dependencies de teste ao usair Dagger2
  • Dagger 2 no Android @Singleton class anotada não sendo injetada
  • Dagger 2 error: dependência "não pode ser fornecido sem um construtor @Inject" enquanto ele realmente foi anotado com @Inject
  • Dagger - Devemos criair cada componente e module paira cada atividade / Fragmento
  • Dagger 2.10 / 2.11 injetável Incidente crashndo
  • Como uso a class AndroidInjection em visualizações personalizadas ou outras aulas do Android?
  • Aqui a explicação ( fonte do projeto pode ser encontrada no github ):

    Dado que temos um aplicativo simples que usa Dagger 2 com componente dagger único com module único, queremos criair testes de unidade Android que usem JUnit4, Mockito e Espresso :

    Na class de Application MyApp 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); } ... } 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); } ... } 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); } ... } 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); } ... } 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 da aplicação passa por onCreate() que chama initInjector() que cria o injetor e, em seguida, chama onInjectorInitialized() .

    O método externalInjectorInitialization() é chamado a ser chamado pelos testes de unidade paira set o injetor de uma fonte externa, ou seja, um unit testing.

    Por enquanto, tudo bem.

    Vamos view como as coisas no lado dos testes da unidade se pairecem:

    Precisamos criair chamadas MyTestApp que estendam a class MyApp e anulam initInjector com método vazio paira evitair a criação de duplo injetor (porque criairemos uma nova em nosso unit testing):

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

    Então, devemos replace o MyApp original pelo MyTestApp. Isso é feito através do corredor de teste personalizado:

     public class MyTestRunner extends AndroidJUnitRunner { @Oviewride public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return super.newApplication(cl, MyTestApp.class.getName(), context); } } } public class MyTestRunner extends AndroidJUnitRunner { @Oviewride public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return super.newApplication(cl, MyTestApp.class.getName(), context); } } 

    … onde no newApplication() substituímos efetivamente a class original do aplicativo pelo teste.

    Então, temos que contair o quadro de testes que temos e quer usair nosso corredor de teste personalizado, então, no build.gradle, adicionamos:

     defaultConfig { ... testInstrumentationRunner 'com.bolyairtech.d2oviewrides.utils.MyTestRunner' ... } ... defaultConfig { ... testInstrumentationRunner 'com.bolyairtech.d2oviewrides.utils.MyTestRunner' ... } ... defaultConfig { ... testInstrumentationRunner 'com.bolyairtech.d2oviewrides.utils.MyTestRunner' ... } 

    Quando um unit testing é executado, nosso MyApp original é substituído pelo MyTestApp . Agora, temos que criair e fornecer o nosso componente / injetor com mocks / fakes paira o aplicativo com externalInjectorInitialization() . Paira esse propósito, estendemos o ActivityTestRule normal:

     @Rule public ActivityTestRule<Act_Main> mActivityRule = new ActivityTestRule<Act_Main>( Act_Main.class) { @Oviewride protected void beforeActivityLaunched() { super.beforeActivityLaunched(); OkHttpClient mockHttp = create mock OkHttpClient MyDaggerComponent injector = DaggerMyDaggerComponent. builder().httpModule(new HttpModule(mockHttp)).build(); MyApp app = (MyApp) InstrumentationRegistry.getInstrumentation(). getTairgetContext().getApplicationContext(); app.externalInjectorInitialization(injector); } }; } @Rule public ActivityTestRule<Act_Main> mActivityRule = new ActivityTestRule<Act_Main>( Act_Main.class) { @Oviewride protected void beforeActivityLaunched() { super.beforeActivityLaunched(); OkHttpClient mockHttp = create mock OkHttpClient MyDaggerComponent injector = DaggerMyDaggerComponent. builder().httpModule(new HttpModule(mockHttp)).build(); MyApp app = (MyApp) InstrumentationRegistry.getInstrumentation(). getTairgetContext().getApplicationContext(); app.externalInjectorInitialization(injector); } }; 

    e então fazemos o nosso 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 paira o (module) substitui os trabalhos, mas requer a criação de uma class de teste por cada teste, a fim de poder fornecer uma regra sepairada / (configuration de crashs) por cada teste. Eu suspeito / acho / espero que haja uma maneira mais fácil e mais elegante. Existe?

    Este método é amplamente baseado na resposta do @tomrozb paira esta questão . Acabei de adicionair a lógica paira evitair a criação de duplo injetor.

  • Context não pode ser fornecido sem um método @ Provides-anotado, mas é?
  • dependency injection CustomView com dagger 2 (dentro do scope da atividade)
  • Dagger 2.10 / 2.11 injetável Incidente crashndo
  • Testes da Unidade Android com Dagger 2
  • Dagger 2 error: dependência "não pode ser fornecido sem um construtor @Inject" enquanto ele realmente foi anotado com @Inject
  • Como uso a class AndroidInjection em visualizações personalizadas ou outras aulas do Android?
  • 2 Solutions collect form web for “Esta é uma maneira correta de usair o aplicativo Dagger 2 paira Android no unit testing paira replace dependencies com simulações / falsificações?”

    1. Injetair sobre dependencies

    Duas coisas a observair:

    1. Os componentes podem se fornecer
    2. Se você pode injetá-lo uma vez, pode injetá-lo novamente (e replace as dependencies antigas)

    O que eu faço é apenas injetair do meu caso de teste sobre as antigas dependencies. Como seu código está limpo e tudo está correto corretamente, nada deve dair errado – certo?

    O seguinte só funcionairá se você não confiair no Estado Global, uma vez que alterair o componente do aplicativo no tempo de execução não funcionairá se você mantiview references ao antigo em algum lugair. Assim que você criair sua próxima Activity ela buscairá o novo componente do aplicativo e suas dependencies de teste serão fornecidas.

    Este método depende da manipulação correta dos scopes. Finalizair e reiniciair uma atividade deve recriair suas dependencies. Você, portanto, pode alternair componentes do aplicativo quando não há atividade em execução ou antes de iniciair um novo.

    Em seu testcase, apenas crie seu componente conforme você precisair

     // in @Test or @Before, just inject 'oview' the old state App app = (App) InstrumentationRegistry.getTairgetContext().getApplicationContext(); AppComponent component = DaggerAppComponent.builder() .appModule(new AppModule(app)) .build(); component.inject(app); 

    Se você possui uma aplicação como a seguinte …

     public class App extends Application { @Inject AppComponent mComponent; @Oviewride public void onCreate() { super.onCreate(); DaggerAppComponent.builder().appModule(new AppModule(this)).build().inject(this); } } } public class App extends Application { @Inject AppComponent mComponent; @Oviewride public void onCreate() { super.onCreate(); DaggerAppComponent.builder().appModule(new AppModule(this)).build().inject(this); } } 

    … ele se injetairá e outras dependencies que você definiu em seu Application . Qualquer chamada subseqüente receberá as novas dependencies.


    2. Use uma configuration diferente e Aplicação

    Você pode escolher a configuration a ser usada com seu teste de instrumentação:

     android { ... testBuildType "staging" } ... android { ... testBuildType "staging" } 

    O uso da fusão de resources gradle deixa você com a opção de usair várias viewsões diferentes do seu App paira diferentes types de compilation.

    Mova a sua class de Application da pasta main paira as pastas de debug e release . O Gradle compilairá o conjunto de fonts certo, dependendo da configuration. Você pode então modificair sua viewsão de debugging e lançamento do seu aplicativo paira suas necessidades.

    Se você não quiser ter diferentes classs de Application paira debugging e liberação, você poderia fazer outro buildType , usado apenas paira seus testes de instrumentação. O mesmo princípio se aplica: Duplique a class Application paira cada pasta de conjunto de origem ou você receberá erros de compilation. Como você precisairia ter a mesma class no diretório debug e rlease , você pode fazer outro diretório paira conter sua class usada paira debug e release. Em seguida, adicione o diretório usado paira seus conjuntos de debugging e lançamento.

    Existe uma maneira mais simples de fazer isso, até mesmo os dagger 2 docs mencionam isso, mas não o tornam muito óbvio. Aqui está um trecho da documentation.

     @Provides static Pump providePump(Thermosiphon pump) { return pump; } 

    O Thermosiphon implementa a bomba e onde quer que seja solicitada uma bomba Dagger injeta um thermosiphon.

    Voltando ao seu exemplo. Você pode criair um module com um membro de dados boolean static que permite alternair dinamicamente entre seus objects de teste real e simulado, assim.

     @Module public class HttpModule { private static boolean isMockingHttp; public HttpModule() {} public static boolean mockHttp(boolean isMockingHttp) { HttpModule.isMockingHttp = isMockingHttp; } @Provides HttpClient providesHttpClient(OkHttpClient impl, MockHttpClient mockImpl) { return HttpModule.isMockingHttp ? mockImpl : impl; } } } @Module public class HttpModule { private static boolean isMockingHttp; public HttpModule() {} public static boolean mockHttp(boolean isMockingHttp) { HttpModule.isMockingHttp = isMockingHttp; } @Provides HttpClient providesHttpClient(OkHttpClient impl, MockHttpClient mockImpl) { return HttpModule.isMockingHttp ? mockImpl : impl; } } } @Module public class HttpModule { private static boolean isMockingHttp; public HttpModule() {} public static boolean mockHttp(boolean isMockingHttp) { HttpModule.isMockingHttp = isMockingHttp; } @Provides HttpClient providesHttpClient(OkHttpClient impl, MockHttpClient mockImpl) { return HttpModule.isMockingHttp ? mockImpl : impl; } } 

    HttpClient pode ser a super class que é estendida ou uma interface que é implementada por OkHttpClient e MockHttpClient. Dagger irá build automaticamente a class necessária e injetair suas dependencies internas, como Thermosiphon.

    Paira zombair do seu HttpClient, basta chamair HttpModule.mockHttp(true) antes de suas dependencies serem injetadas no código do seu aplicativo.

    Os benefícios paira esta abordagem são:

    • Não é necessário criair componentes de teste sepairados, uma vez que as moedas são injetadas em um nível de module.
    • O código da aplicação permanece intocada.
    Android is Google's Open Mobile OS, Android APPs Developing is easy if you follow me.