android - tag - gtm console
¿Puedo extender una aplicación personalizada en espresso? (4)
Estoy intentando configurar Dagger en mis pruebas de instrumentación Espresso para simular llamadas a recursos externos (servicios REST en este caso). El patrón que seguí en Robolectric para las pruebas de mi unidad fue ampliar mi clase de Aplicación de producción y anular los módulos Dagger con módulos de prueba que devolverían simulacros. Estoy intentando hacer lo mismo aquí, pero recibo una excepción ClassCastException en mis pruebas de Espresso cuando intento convertir la aplicación en mi aplicación personalizada.
Aquí está mi configuración hasta ahora:
Producción
Bajo app / src / main / java / com / mypackage / injection tengo:
MyCustomApplication
package com.mypackage.injection;
import android.app.Application;
import java.util.ArrayList;
import java.util.List;
import dagger.ObjectGraph;
public class MyCustomApplication extends Application {
protected ObjectGraph graph;
@Override
public void onCreate() {
super.onCreate();
graph = ObjectGraph.create(getModules().toArray());
}
protected List<Object> getModules() {
List<Object> modules = new ArrayList<Object>();
modules.add(new AndroidModule(this));
modules.add(new RemoteResourcesModule(this));
modules.add(new MyCustomModule());
return modules;
}
public void inject(Object object) {
graph.inject(object);
}
}
Que utilizo de la siguiente manera:
BaseActividad
package com.mypackage.injection.views;
import android.app.Activity;
import android.os.Bundle;
import com.mypackage.injection.MyCustomApplication;
public abstract class MyCustomBaseActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((MyCustomApplication)getApplication()).inject(this);
}
}
Actividad a prueba
package com.mypackage.views.mydomain;
// imports snipped for bevity
public class MyActivity extends MyBaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//snip
}
}
Configuración de espresso
Bajo app / src / androidTest / java / com / mypackage / injection tengo:
MyCustomEspressoApplication
package com.mypackage.injection;
import java.util.ArrayList;
import java.util.List;
import dagger.ObjectGraph;
public class MyCustomEspressoApplication extends MyCustomApplication {
private AndroidModule androidModule;
private MyCustomModule myCustomModule;
private EspressoRemoteResourcesModule espressoRemoteResourcesModule;
@Override
public void onCreate() {
super.onCreate();
graph = ObjectGraph.create(getModules().toArray());
}
protected List<Object> getModules() {
List<Object> modules = new ArrayList<Object>();
modules.add(getAndroidModule());
modules.add(getEspressoRemoteResourcesModule());
modules.add(getMyCustomModule());
return modules;
}
public void inject(Object object) {
graph.inject(object);
}
public AndroidModule getAndroidModule() {
if (this.androidModule == null) {
this.androidModule = new AndroidModule(this);
}
return this.androidModule;
}
public MyCustomModule getMyCustomModule() {
if (this.myCustomModule == null) {
this.myCustomModule = new MyCustomModule();
}
return this.myCustomModule;
}
public EspressoRemoteResourcesModule getEspressoRemoteResourcesModule() {
if (this.espressoRemoteResourcesModule == null) {
this.espressoRemoteResourcesModule = new EspressoRemoteResourcesModule();
}
return this.espressoRemoteResourcesModule;
}
}
Mi prueba de espresso, bajo app / src / android Test / com / mypackage / espresso:
package com.mypackage.espresso;
// imports snipped for brevity
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MyActivityTest extends ActivityInstrumentationTestCase2<MyActivity>{
private MyActivity myActivity;
public MyActivityTest() {
super(MyActivity.class);
}
@Before
public void setUp() throws Exception {
super.setUp();
injectInstrumentation(InstrumentationRegistry.getInstrumentation());
myActivity = getActivity();
}
@After
public void tearDown() throws Exception {
super.tearDown();
}
@Test
public void testWhenTheActionBarButtonIsPressedThenThePlacesAreListed() {
//The next line is where the runtime exception occurs.
MyCustomEspressoApplication app = (MyCustomEspressoApplication)getInstrumentation().getTargetContext().getApplicationContext();
//I''ve also tried getActivity().getApplication() and
// getActivity.getApplicationContext() with the same results
//snip
}
}
Mi AndroidManifest.xml
(He visto muchas respuestas con respecto a la ClassCastException en las clases de aplicaciones personalizadas anteriormente, y la mayoría de ellas apuntan a una propiedad "android: name" que falta en el nodo de la aplicación. Estoy pegando esto aquí para mostrar que este no es el caso Por lo que yo puedo decir.)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.mypackage">
<!-- snip -->
<application
android:name=".injection.MyCustomApplication"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<!-- snip -->
</application>
<!-- snip -->
</manifest>
construir.gradle
buildscript {
repositories {
mavenCentral()
jcenter()
}
}
apply plugin: ''com.android.application''
apply plugin: ''idea''
android {
testOptions {
unitTests.returnDefaultValues = true
}
lintOptions {
abortOnError false
}
packagingOptions {
exclude ''LICENSE.txt''
exclude ''META-INF/LICENSE''
exclude ''META-INF/LICENSE.txt''
exclude ''META-INF/NOTICE''
}
compileSdkVersion 21
buildToolsVersion "21.1.2"
defaultConfig {
applicationId "com.mypackage"
minSdkVersion 15
targetSdkVersion 21
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
idea {
module {
testOutputDir = file(''build/test-classes/debug'')
}
}
dependencies {
compile project('':swipeablecardview'')
compile fileTree(dir: ''libs'', include: [''*.jar''])
compile ''com.android.support:support-annotations:21.0.3''
compile ''com.android.support:appcompat-v7:21.0.3''
compile ''com.squareup:javawriter:2.5.0''
compile (''com.squareup.dagger:dagger:1.2.2'') {
exclude module: ''javawriter''
}
compile (''com.squareup.dagger:dagger-compiler:1.2.2'') {
exclude module: ''javawriter''
}
compile ''com.melnykov:floatingactionbutton:1.1.0''
compile ''com.android.support:cardview-v7:21.0.+''
compile ''com.android.support:recyclerview-v7:21.0.+''
// compile ''se.walkercrou:google-places-api-java:2.1.0''
compile ''org.apache.httpcomponents:httpclient-android:4.3.5.1''
compile ''commons-io:commons-io:1.3.2''
testCompile ''org.hamcrest:hamcrest-integration:1.3''
testCompile ''org.hamcrest:hamcrest-core:1.3''
testCompile ''org.hamcrest:hamcrest-library:1.3''
testCompile(''junit:junit:4.12'')
testCompile ''org.mockito:mockito-core:1.+''
testCompile(''org.robolectric:robolectric:3.0-SNAPSHOT'')
testCompile(''org.robolectric:shadows-support-v4:3.0-SNAPSHOT'')
androidTestCompile ''org.mockito:mockito-core:1.+''
androidTestCompile(''com.android.support.test.espresso:espresso-core:2.0'') {
exclude group: ''javax.inject''
exclude module: ''javawriter''
}
androidTestCompile(''com.android.support.test:testing-support-lib:0.1'')
}
El stacktrace:
java.lang.ClassCastException: com.mypackage.injection.MyCustomApplication no se puede convertir en el nombre de la aplicación. invokeNative (Método nativo) en java.lang.reflect.Method.invoke (Method.java:511) en org.junit.runners.model.FrameworkMethod $ 1.runReflectiveCall (FrameworkMethod.java:45) en org.junit.internal.runners .model.ReflectiveCallable.run (ReflectiveCallable.java:15) en org.junit.runners.model.FrameworkMethod.invokeExplosively (FrameworkMethod.java:42) en org.junit.internal.runners.statements.InvokeMethod.viv.viv.viv. : 20) en org.junit.internal.runners.statements.RunBefores.evaluate (RunBefores.java:28) en org.junit.internal.runners.statements.RunAfters.evaluate (RunAfters.java:30) en org.junit. runners.ParentRunner.runLeaf (ParentRunner.java:263) en org.junit.runners.BlockJUnit4ClassRunner.runChild ( BlockJUnit4ClassRunner.java:68) en org.junit.runners.BlockJUnit4ClassRunner.runChild (BlockJUnit4ClassRunner.java:47) en el grupo de personas. .schedule (ParentRunner.java:60) en org.junit.runners.ParentRunner.runChildren (ParentRunner.java:229) en org.junit.runners.ParentRunner.access $ 000 (ParentRunner.java:50) en org.junit.runners .ParentRunner $ 2.evaluate (ParentRunner.java:222) en org.junit.runners.ParentRunner.run (ParentRunner.java:300) en org.junit.runners.Suite.runChild (Suite.java:128) en org.junit .runners.Suite.runChild (Suite.java:24) en org.junit.runners.ParentRunner $ 3.run (ParentRunner.java:231) en org.junit.runners.ParentRunner $ 1.schedule (ParentRunner.java:60) en org.junit.runners.ParentRunner.runChildren (ParentRunner.java:229) en org.junit.runners.ParentRunner.access $ 000 (ParentRunner.java:50) en org.junit.runners.ParentRunner $ 2.evaluate.Personal 222) en org.junit.runners.ParentRunner.run (ParentRunner. java: 300) en org.junit.runner.JUnitCore.run (JUnitCore.java:157) en org.junit.runner.JUnitCore.run (JUnitCore.java:136) en android.support.test.runner.AndroidJUnitRunner.onStart (AndroidJUnitRunner.java:270) en android.app.Instrumentation $ InstrumentationThread.run (Instrumentation.java:1551)
He leído los documentos de Espresso y Dagger y he buscado en Github sin éxito alguno. Apreciaría cualquier ayuda que alguien pueda proporcionar. Gracias por adelantado.
Editar # 1
Seguí la sugerencia de Daniel de extender el corredor de prueba y verificar el VerifyError, y obtuve el siguiente seguimiento de pila:
java.lang.ExceptionInInitializerError
at org.mockito.internal.creation.cglib.ClassImposterizer.createProxyClass(ClassImposterizer.java:95)
at org.mockito.internal.creation.cglib.ClassImposterizer.imposterise(ClassImposterizer.java:57)
at org.mockito.internal.creation.cglib.ClassImposterizer.imposterise(ClassImposterizer.java:49)
at org.mockito.internal.creation.cglib.CglibMockMaker.createMock(CglibMockMaker.java:24)
at org.mockito.internal.util.MockUtil.createMock(MockUtil.java:33)
at org.mockito.internal.MockitoCore.mock(MockitoCore.java:59)
at org.mockito.Mockito.mock(Mockito.java:1285)
at org.mockito.Mockito.mock(Mockito.java:1163)
at com.mypackage.injection.EspressoRemoteResourcesModule.<init>(EspressoRemoteResourcesModule.java:17)
at com.mypackage.injection.MyCustomEspressoApplication.getEspressoRemoteResourcesModule(MyCustomEspressoApplication.java:52)
at com.mypackage.injection.MyCustomEspressoApplication.getModules(MyCustomEspressoApplication.java:24)
at com.mypackage.injection.MyCustomApplication.onCreate(MyCustomApplication.java:18)
at com.mypackage.injection.MyCustomEspressoApplication.onCreate(MyCustomEspressoApplication.java:16)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:999)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4151)
at android.app.ActivityThread.access$1300(ActivityThread.java:130)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1255)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4745)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.VerifyError: org/mockito/cglib/core/ReflectUtils
at org.mockito.cglib.core.KeyFactory$Generator.generateClass(KeyFactory.java:167)
at org.mockito.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
at org.mockito.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:217)
at org.mockito.cglib.core.KeyFactory$Generator.create(KeyFactory.java:145)
at org.mockito.cglib.core.KeyFactory.create(KeyFactory.java:117)
at org.mockito.cglib.core.KeyFactory.create(KeyFactory.java:109)
at org.mockito.cglib.core.KeyFactory.create(KeyFactory.java:105)
at org.mockito.cglib.proxy.Enhancer.<clinit>(Enhancer.java:70)
at org.mockito.internal.creation.cglib.ClassImposterizer.createProxyClass(ClassImposterizer.java:95)
at org.mockito.internal.creation.cglib.ClassImposterizer.imposterise(ClassImposterizer.java:57)
at org.mockito.internal.creation.cglib.ClassImposterizer.imposterise(ClassImposterizer.java:49)
at org.mockito.internal.creation.cglib.CglibMockMaker.createMock(CglibMockMaker.java:24)
at org.mockito.internal.util.MockUtil.createMock(MockUtil.java:33)
at org.mockito.internal.MockitoCore.mock(MockitoCore.java:59)
at org.mockito.Mockito.mock(Mockito.java:1285)
at org.mockito.Mockito.mock(Mockito.java:1163)
at com.mypackage.injection.EspressoRemoteResourcesModule.<init>(EspressoRemoteResourcesModule.java:17)
at com.mypackage.injection.MyCustomEspressoApplication.getEspressoRemoteResourcesModule(MyCustomEspressoApplication.java:52)
at com.mypackage.injection.MyCustomEspressoApplication.getModules(MyCustomEspressoApplication.java:24)
at com.mypackage.injection.MyCustomApplication.onCreate(MyCustomApplication.java:18)
at com.mypackage.injection.MyCustomEspressoApplication.onCreate(MyCustomEspressoApplication.java:16)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:999)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4151)
at android.app.ActivityThread.access$1300(ActivityThread.java:130)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1255)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4745)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
at dalvik.system.NativeStart.main(Native Method)
04-29 06:40:28.594 1016-1016/? W/ActivityManager﹕ Error in app com.mypackage running instrumentation ComponentInfo{com.mypackage.test/com.mypackage.EspressoTestRunner}:
04-29 06:40:28.594 1016-1016/? W/ActivityManager﹕ java.lang.VerifyError
04-29 06:40:28.594 1016-1016/? W/ActivityManager﹕ java.lang.VerifyError: org/mockito/cglib/core/ReflectUtils
Esto me señaló a Mockito. Me faltaban las bibliotecas necesarias de mockito y dexmaker.
Actualicé mis dependencias a:
androidTestCompile ''org.mockito:mockito-core:1.10.19''
androidTestCompile ''com.google.dexmaker:dexmaker:1.2''
androidTestCompile (''com.google.dexmaker:dexmaker-mockito:1.2'') {
exclude module: ''hamcrest-core''
exclude module: ''mockito-core''
}
androidTestCompile(''com.android.support.test.espresso:espresso-core:2.0'') {
exclude group: ''javax.inject''
}
También anulé MyCustomModule, que necesitaba incluir EspressoRemoteResourcesModule. Una vez que hice esto las cosas empezaron a funcionar.
Con un corredor de instrumentación personalizado, puede anular newApplication
y hacer que newApplication
una instancia de la aplicación por defecto que no sea la aplicación predeterminada.
public class MyRunner extends AndroidJUnitRunner {
@Override
public Application newApplication(ClassLoader cl, String className, Context context)
throws Exception {
return super.newApplication(cl, MyCustomEspressoApplication.class.getName(), context);
}
}
Asegúrese de actualizar testInstrumentationRunner
con el nombre de su corredor personalizado.
Me tomó un día entero para obtener la respuesta completa.
Paso 1: anular AndroidJUnitRunner
public class TestRunner extends AndroidJUnitRunner
{
@Override
public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return super.newApplication(cl, TestApplication.class.getName(), context);
}
}
Paso 2: reemplaza el AndroidJunitRunner existente en build.gradle
defaultConfig {
...
// testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunner ''com.xi_zz.androidtest.TestRunner''
}
Paso 3: Agregue com.android.support.test: runner to build.gradle
androidTestCompile ''com.android.support.test:runner:0.5''
androidTestCompile ''com.android.support.test.espresso:espresso-core:2.2.2''
Paso 4: Solo si tienes este error
Warning:Conflict with dependency ''com.android.support:support-annotations''. Resolved versions for app (25.2.0) and test app (23.1.1) differ. See http://g.co/androidstudio/app-test-app-conflict for details.
Luego, agregue una línea más:
androidTestCompile ''com.android.support:support-annotations:25.2.0''
androidTestCompile ''com.android.support.test:runner:0.5''
androidTestCompile ''com.android.support.test.espresso:espresso-core:2.2.2''
Finalmente, prueba si funciona.
@RunWith(AndroidJUnit4.class)
public class MockApplicationTest
{
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);
@Test
public void testApplicationName() throws Exception
{
assertEquals("TestApplication", mActivityRule.getActivity().getApplication().getClass().getSimpleName());
}
}
No he probado esto ampliamente en todos los casos, pero puede probar una Regla personalizada para especificar su clase de Aplicación personalizada por caso de prueba en lugar de todos los casos de prueba aplicados por el corredor personalizado. He tenido éxito con los siguientes en casos simples:
public class ApplicationTestRule<T extends Application> extends UiThreadTestRule {
Class<T> appClazz;
boolean wait = false;
T app;
public ApplicationTestRule(Class<T> applicationClazz) {
this(applicationClazz, false);
}
public ApplicationTestRule(Class<T> applicationClazz, boolean wait) {
this.appClazz = applicationClazz;
this.wait = wait;
}
@Override
public Statement apply(final Statement base, Description description) {
return new ApplicationStatement(super.apply(base, description));
}
private void terminateApp() {
if (app != null) {
app.onTerminate();
}
}
public void createApplication() throws IllegalAccessException, ClassNotFoundException, InstantiationException {
app = (T) InstrumentationRegistry.getInstrumentation().newApplication(this.getClass().getClassLoader(), appClazz.getName(), InstrumentationRegistry.getInstrumentation().getTargetContext());
InstrumentationRegistry.getInstrumentation().callApplicationOnCreate(app);
}
private class ApplicationStatement extends Statement {
private final Statement mBase;
public ApplicationStatement(Statement base) {
mBase = base;
}
@Override
public void evaluate() throws Throwable {
try {
if (!wait) {
createApplication();
}
mBase.evaluate();
} finally {
terminateApp();
app = null;
}
}
}
}
Luego, en tu caso de prueba, crea la regla:
@Rule
public ApplicationTestRule<TestApplication> appRule = new ApplicationTestRule<>(TestApplication.class,true);
Tenga en cuenta que el segundo parámetro es opcional. Si es falso o está apagado, la aplicación personalizada se crea cada vez antes de cada caso de prueba. Si se establece en verdadero, debe llamar a appRule.createApplication()
antes de la lógica de la aplicación.
Si está probando un módulo de biblioteca, puede crear una clase de aplicación personalizada y registrarla en el manifiesto del paquete de prueba:
root / library-module / src / androidTest / AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:name="path.to.TestApplication" />
</manifest>
No hay reglas, no hay corredores.