web-dev-qa-db-ja.com

Espressoでカスタムアプリケーションを拡張できますか?

EspressoインストゥルメンテーションテストでDaggerを設定して、外部リソース(この場合はRESTfulサービス)への呼び出しを模擬しようとしています。私が単体テストのためにRobolectricで従ったパターンは、本番のApplicationクラスを拡張し、モックを返すテストモジュールでDaggerモジュールをオーバーライドすることでした。ここでも同じことを試みていますが、アプリケーションをカスタムアプリケーションにキャストしようとすると、EspressoテストでClassCastExceptionが発生します。

これまでの私のセットアップは次のとおりです。

生産

App/src/main/Java/com/mypackage/injectionの下に:

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

Espresso Setup

App/src/androidTest/Java/com/mypackage/injectionの下に:

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

App/src/androidTest/com/mypackage/espressoにある私の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
     }
}

My AndroidManifest.xml

(私は以前にカスタムアプリケーションクラスのClassCastExceptionに関する多くの回答を見てきましたが、それらのほとんどは、アプリケーションノードで「Android:name」プロパティが欠落していることを示しています。これをここに貼り付けて、そうではないことを示します私の知る限り。)

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

スタックトレース:

Java.lang.ClassCastException:com.mypackage.injection.MyCustomApplicationをcom.mypackage.injection.MyCustomEspressoApplication(com.mypackage.espresso.MyActivityTest.testWhenTheActionBarButtonIsPressedThenThePlacesAreListed(MyActivityTest.Java:107)at Java.lang.reflect.Method。にキャストできないinvokeNative(Native Method)at Java.lang.reflect.Method.invoke(Method.Java:511)at org.junit.runners.model.FrameworkMethod $ 1.runReflectiveCall(FrameworkMethod.Java:45)at org.junit.internal.runners .model.ReflectiveCallable.run(ReflectiveCallable.Java:15)at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.Java:42)at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.Java :20)org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.Java:28)at org.junit.internal.runners.statements.RunAfters.eAfterate(RunAfters.Java:30)at org.junit。 org.junit.runners.BlockJUnit4ClassRunner.runChild(のrunners.ParentRunner.runLeaf(ParentRunner.Java:263) BlockJUnit4ClassRunner.Java:68)org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.Java:47)at org.junit.runners.ParentRunner $ 3.run(ParentRunner.Java:231)at org.junit.runners.ParentRunner $ 1 .schedule(ParentRunner.Java:60)at org.junit.runners.ParentRunner.runChildren(ParentRunner.Java:229)at org.junit.runners.ParentRunner.access $ 000(ParentRunner.Java:50)at org.junit.runners .ParentRunner $ 2.evaluate(ParentRunner.Java:222)(org.junit.runners.ParentRunner.run(ParentRunner.Java:300)at org.junit.runners.Suite.runChild(Suite.Java:128)(org.junit) .runners.Suite.runChild(Suite.Java:24)at org.junit.runners.ParentRunner $ 3.run(ParentRunner.Java:231)at org.junit.runners.ParentRunner $ 1.schedule(ParentRunner.Java:60)at org.junit.runners.ParentRunner.access $ 000(ParentRunner.Java:50)at org.junit.runners.ParentRunner $ 2.evaluate(ParentRunner.Java:にあるorg.junit.runners.ParentRunner.runChildren(ParentRunner.Java:229) 222)org.junit.runners.ParentRunner.run(ParentRunner。 Java:300)org.junit.runner.JUnitCore.run(JUnitCore.Java:157)at org.junit.runner.JUnitCore.run(JUnitCore.Java:136)at Android.support.test.runner.AndroidJUnitRunner.onStart (AndroidJUnitRunner.Java:270)at Android.app.Instrumentation $ InstrumentationThread.run(Instrumentation.Java:1551)

私はEspressoとDaggerのドキュメントを読み、Githubで問題を検索しましたが、役に立ちませんでした。だれでも提供できるヘルプがあれば幸いです。前もって感謝します。

編集#1

Danielの提案に従い、テストランナーを拡張して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および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'
}

また、EspressoRemoteResourcesModuleを含める必要があるMyCustomModuleもオーバーライドしました。これを行うと、物事が機能し始めました。

27
jameskbride

カスタムインストルメンテーションランナーを使用すると、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をカスタムランナーの名前で更新してください。

32
Daniel Lubarov

完全な答えを得るために丸一日かかった。

ステップ1: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:build.gradleの既存のAndroidJunitRunnerを置き換える

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

ステップ3:com.Android.support.test:runnerをbuild.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.

次に、1行追加します。

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());
    }
}
20
Xi Wei

ライブラリモジュールをテストする場合は、カスタムアプリケーションクラスを作成して、テストパッケージマニフェストに登録できます。

root/library-module/src/androidTest/AndroidManifest.xml

<manifest xmlns:Android="http://schemas.Android.com/apk/res/Android">
    <application Android:name="path.to.TestApplication" />
</manifest>

ルールなし、ランナーなし。

5
Eugen Pechanec

私はこれを広範囲に試したわけではありませんが、カスタムルールを試して、カスタムランナーによって適用されるすべてのテストケースではなく、テストケースごとにカスタムアプリケーションクラスを指定できます。私は簡単なケースで次のことで成功しました:

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

2番目のパラメーターはオプションです。 falseまたは省略した場合、カスタムアプリケーションは各テストケースの前に毎回作成されます。 trueに設定した場合、アプリケーションロジックの前にappRule.createApplication()を呼び出す必要があります。

5
JCricket