Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
135 views
in Technique[技术] by (71.8m points)

android - Can I extend a custom Application in Espresso?

I'm attempting to set up Dagger in my Espresso instrumentation tests in order to mock out calls to external resources (RESTful services in this case). The pattern I followed in Robolectric for my unit testing was to extend my production Application class and override the Dagger modules with test modules which will return mocks. I'm attempting to do the same thing here, but I'm getting a ClassCastException in my Espresso tests when I try to cast the application to my custom application.

Here is my set up thus far:

Production

Under app/src/main/java/com/mypackage/injection I have:

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

Which I use in the following manner:

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

}

Activity under test

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

Under app/src/androidTest/java/com/mypackage/injection I have:

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

My Espresso test, under app/src/androidTest/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
     }
}

My AndroidManifest.xml

(I've seen lots of answers regarding the ClassCastException in custom Application classes before, and most of them point to a missing "android:name" property on the Application node. I'm pasting this here to show that this is not the case as far as I can tell.)

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

The stacktrace:

java.lang.ClassCastException: com.mypackage.injection.MyCustomApplication cannot be cast to com.mypackage.injection.MyCustomEspressoApplication at 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) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28) at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:30) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68) at 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) at org.junit.runners.ParentRunner.run(ParentRunner.java:300) at org.junit.runners.Suite.runChild(Suite.java:128) at 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.runChildren(ParentRunner.java:229) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222) at org.junit.runners.ParentRunner.run(ParentRunner.java:300) at 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)

I've read through the Espresso and Dagger docs and searched through issues on Github to no avail. I'd appreciate any help anyone can provide. Thanks in advance.

Edit #1

I followed Daniel's suggestion to extend the test runner and checkout the VerifyError, and got the following stack trace:

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

With a custom instrumentation runner, you can override newApplication and have it instantiate something other than the default application from the manifest.

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

Be sure to update testInstrumentationRunner with your custom runner's name.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...