2015-04-20 3 views
13

Я пытаюсь настроить кинжал на своих инструментальных тестах эспрессо, чтобы издеваться над внешними ресурсами (в этом случае RESTful-сервисы). Образец, который я последовал в Robolectric для моего модульного тестирования, заключался в расширении моего класса Application Application и переопределении модулей Dagger с помощью тестовых модулей, которые возвратят насмешки. Я пытаюсь сделать то же самое здесь, но я получаю ClassCastException в своих тестах Espresso, когда пытаюсь применить приложение к своему пользовательскому приложению.Могу ли я расширить пользовательское приложение в Espresso?

Вот мой набор до сих пор:

Производство

Под приложение/SRC/главная/Java/COM/MyPackage/инъекции у меня есть:

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

которые я использую следующим образом:

BaseActivity

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

} 

активность тестируемой

package com.mypackage.views.mydomain; 
// imports snipped for bevity 

public class MyActivity extends MyBaseActivity { 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     //snip 
    } 
} 

Эспрессо Настройка

Под приложение/SRC/androidTest/Java/COM/MyPackage/инъекции у меня есть:

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

Мой тест Эспрессо под приложение/SRC/androidTest/ком/MyPackage/эспрессо:

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

Мои AndroidManifest.xml

(я видел много ответов в отношении ClassCastException в пользовательских классах приложений раньше, и большинство из них указывают на отсутствие свойства «android: name» на узле Application. Я вставив это здесь, чтобы показать, что это не так, насколько я могу сказать)

<?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> 

build.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') 
} 

StackTrace:.

java.lang.ClassCastException: com.mypackage.injection.MyCustomApplication не может быть отлита до com.mypackage.injection.MyCustomEspressoApplication на com.mypackage.espresso.MyActivityTest.testWhenTheActionBarButtonIsPressedThenThePlacesAreListed (MyActivityTest.java:107) на java.lang.reflect.Method.invokeNative (нативный метод) при java.lang.reflect.Method.invoke (Method.java:511) на org.junit.runners.model.FrameworkMethod $ 1.runReflectiveCall (FrameworkMethod.java:45) на org.junit.internal.runners.model.ReflectiveCallable.run (ReflectiveCallable.java:15) на org.junit.runners.model.FrameworkMethod.invokeExplosively (FrameworkMethod.java: 42) на org.junit.internal.runners.statements.InvokeMethod.evaluate (InvokeMethod.java:20) на org.junit.internal.runners.statements.RunBefores.evaluate (RunBefores.java:28) на org.junit.internal.runners.statements.RunAfters.evaluate (RunAfters.java:30) на org.junit.runners.ParentRunner.runLeaf (ParentRunner.java:263) при org.junit.runners.BlockJUnit4ClassRunner.runChild (BlockJUnit4ClassRunner.java:68)на org.junit.runners.BlockJUnit4ClassRunner.runChild (BlockJUnit4ClassRunner.java:47) в org.junit.runners.ParentRunner $ 3.run (ParentRunner.java:231) в org.junit.runners.ParentRunner $ 1.schedule (ParentRunner.java:60) в org.junit.runners.ParentRunner.runChildren (ParentRunner.java:229) в org.junit.runners.ParentRunner.access $ 000 (ParentRunner.java:50) в org.junit. runners.ParentRunner $ 2.оценить (ParentRunner.java:222) в org.junit.runners.ParentRunner.run (ParentRunner.java:300) в org.junit.runners.Suite.runChild (Suite.java:128) at org.junit.runners.Suite.runChild (Suite.java:24) в org.junit.runners.ParentRunner $ 3.run (ParentRunner.java:231) в org.junit.runners.ParentRunner $ 1.schedule (ParentRunner.java:60) при org.junit.runners.ParentRunner.runChildren (ParentRunner.java:229) на org.junit.runners.ParentRunner.access $ 000 (ParentRunner .java: 50) at org.junit.runners.ParentRunner $ 2.оценить (ParentRunner.java:222) в org.junit.runners.ParentRunner.run (ParentRunner.java:300) в org.junit.runner. JUnitCore.run (JUnitCore.java:157) при org.junit.runner.JUnitCore.run (JUnitCore.java:136) при android.support.test.runner.AndroidJUnitRunner.onStart (AndroidJUnitRunner.java:270) на android.app.Инструментация $ InstrumentationThread.run (Instrumentation.java:1551)

Я прочитал документы эспрессо и кинжала и искал проблемы в Github безрезультатно. Буду признателен за любую помощь, которую любой может предоставить. Заранее спасибо.

Редактировать # 1

Я последовал предложение Даниила о продлении испытательного бегуна и чекает VerifyError, и получил следующую трассировку стеки:

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 

Это указало мне на Mockito. Мне не хватало необходимых библиотек mockito и dexmaker.

Я обновил свои зависимости для:

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' 
} 

I также отменяют MyCustomModule, которые необходимо включить EspressoRemoteResourcesModule. Как только я это сделал, это начало работать.

ответ

22

С помощью специального инструментария вы можете переопределить newApplication и создать экземпляр чего-либо, кроме приложения по умолчанию, из манифеста.

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

Не забудьте обновить testInstrumentationRunner с именем вашего пользовательского бегуна.

+0

Пробовал это, теперь я получаю: Тесты на nexus_5_android_4_0_3 (AVD) - 4.1.1 не удалось: запуск инструментария завершился неудачно из-за «java.lang.VerifyError» : app: connectedAndroidTest FAILED com.android.builder.testing.ConnectedDevice> hasTests [nexus_5_android_4_0_3 (AVD) - 4.1 .1] [31mFAILED [0m Не найдено ни одного теста. Кажется, это новая земля. Есть ли другой метод, который мне нужно переопределить, чтобы он обнаружил тесты? – jameskbride

+0

Можете ли вы вставить полный 'VerifyError' (должен быть в logcat, я думаю) и любые предупреждения' dalvikvm'? 'Не найденные тесты 'обычно означает, что во время установки бегуна произошла ошибка, прежде чем испытания начались. –

+0

См. Мое редактирование выше. Версия TL; DR - это то, что вы указали мне в правильном направлении, расширив тестовый бегун и проверив VerifyError, который указал мне на Mockito и Dexmaker. Спасибо за помощь! – jameskbride

3

Я не пробовал все это в целом, но вы можете попробовать собственное правило, чтобы указать свой собственный класс приложения для каждого тестового примера, а не для всех тестовых примеров, применяемых пользовательским бегуном. Я имел успех со следующим в простых случаях:

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

Затем в тесте, создайте правило:

@Rule 
public ApplicationTestRule<TestApplication> appRule = new ApplicationTestRule<>(TestApplication.class,true); 

Обратите внимание на второй параметр является необязательным. Если значение false или прекращено, пользовательское приложение создается каждый раз перед каждым тестовым случаем. Если установлено значение true, вам нужно позвонить appRule.createApplication() перед вашей логикой приложения.

+0

Обратите внимание, что при этом будет создано приложение, но это не помешает первому классу исходного кода запускаться самостоятельно. Ответ Дэниела выше заменяет фактический бегун инструментария, позволяя вам вводить свой класс прикладного класса каждый раз, когда запрашивается приложение, а не только во время отдельных правил тестирования. – Joe

6

Понадобился целый день, чтобы получить полный ответ.

Шаг 1: Override 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); 
    } 
} 

Шаг 2: заменить существующий AndroidJunitRunner в build.gradle

defaultConfig { 
    ... 
    // testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 
    testInstrumentationRunner 'com.xi_zz.androidtest.TestRunner' 
} 

Шаг 3: Добавить com.android.support.test : бегун для сборки.gradle

androidTestCompile 'com.android.support.test:runner:0.5' 
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2' 

Шаг 4: Только если вы получили эту ошибку

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. 

Затем добавьте еще одну строку:

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' 

Наконец, тест, если он работает

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

Ты спас мне мой день! Благодарю.Я столкнулся с ошибкой «Не удалось выполнить проверку: не удалось найти информацию о контроле для: ComponentInfo {com.example.myapp.debug.test/android.support.test.runner.AndroidJUnitRunner}", хотя я использовал пользовательский тестовый бегун, как вы предложили , Решение для этого - удалить конфигурацию ('android studio -> Run> Edit Configurations> Удалить все под Android Instrumentation Tests'). Теперь запустите тесты еще раз. – rpattabi

+0

@rpattabi Я удалил все, но снова получил ошибку :(и вся конфигурация вернулась. Что случилось с Android-студией! Я пытаюсь сделать это с помощью консоли, но я тоже получаю ошибку. –

Смежные вопросы