¿Es esta una forma correcta de usar Dagger 2 para la aplicación de Android en pruebas unitarias para anular dependencias con simulacros / falsificaciones?
Para un proyecto Java 'regular', es fácil anular las dependencias en las pruebas unitarias con simulaciones / falsificaciones. Simplemente tienes que construir tuDaga componente y dárselo a la clase 'principal' que impulsa su aplicación.
Para Android las cosas sonno tan simple y he buscado durante mucho tiempo un ejemplo decente, pero no pude encontrarlo, así que tuve que crear mi propia implementación y realmente agradeceré los comentarios: esta es una forma correcta de usar Dagger 2 o hay una manera más simple / elegante para anular las dependencias.
Aquí la explicación (la fuente del proyecto se puede encontrar en github):
Dado que tenemos una aplicación simple que usa Dagger 2 con un solo componente de daga con un solo módulo, queremos crear pruebas unitarias de Android que usen JUnit4,Mockito yCafé exprés:
En elMyApp
Application
clase el componente / inyector se inicializa así:
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);
}
...
En el código anterior: el inicio normal de la aplicación pasaonCreate()
que llamainitInjector()
que crea el inyector y luego llamaonInjectorInitialized()
.
losexternalInjectorInitialization()
El método es llamado por las pruebas unitarias paraset
el inyector de una fuente externa, es decir, una prueba unitaria.
Hasta aquí todo bien.
Veamos cómo se ven las cosas en el lado de las pruebas unitarias:
Necesitamos crear llamadas MyTestApp que extiendan la clase MyApp y anuleninitInjector
con método vacío para evitar la creación de inyectores dobles (porque crearemos uno nuevo en nuestra prueba de unidad):
public class MyTestApp extends MyApp {
@Override
protected void initInjector() {
// empty
}
}
Luego tenemos que reemplazar de alguna manera el MyApp original con MyTestApp. Esto se realiza a través de un corredor de prueba 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);
}
}
... donde ennewApplication()
reemplazamos efectivamente la clase de aplicación original con la de prueba.
Luego tenemos que decirle al marco de prueba que tenemos y queremos usar nuestro corredor de prueba personalizado, así que en el build.gradle agregamos:
defaultConfig {
...
testInstrumentationRunner 'com.bolyartech.d2overrides.utils.MyTestRunner'
...
}
Cuando se ejecuta una prueba unitaria nuestro originalMyApp
se reemplaza conMyTestApp
. Ahora tenemos que crear y proporcionar a nuestro componente / inyector simulacros / falsificaciones a la aplicación conexternalInjectorInitialization()
. Para ese propósito, ampliamos la 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);
}
};
y luego hacemos nuestra prueba de la manera habitual:
@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)));
}
El método anterior para (anulaciones) del módulo funciona, pero requiere crear una clase de prueba por cada prueba para poder proporcionar una regla / (simulación de configuración) por separado para cada prueba. Sospecho / adivino / espero que haya una manera más fácil y elegante. ¿Esta ahí?
Este método se basa en gran medida en la respuesta de @tomrozb paraesta pregunta. Acabo de agregar la lógica para evitar la creación de inyectores dobles.