Search code examples
androidjunitandroid-manifestmetadataandroid-instrumentation

Specifying custom RunListener in AndroidManifest metadata not working


Goal

To have a custom RunListener do custom things on test failure when running Android instrumentation tests using Espresso.

tl;dr

InstrumentationInfo.metaData is null, even while ApplicationInfo.metaData has my info. Why?

Progress thus far

I can get my RunListener to work with the following adb command:

adb shell am instrument -w -e listener com.myproject.test.runlisteners.CustomRunListener -e class com.myproject.test.ui.HomeActivityTest#testWillFail com.myproject.test/android.support.test.runner.AndroidJUnitRunner

Which is specified in the documentation for AndroidJUnitRunner here.

However, that documentation also states that one can specify a RunListener in an AndroidManifest.xml metadata element. I have thus far been unsuccessful getting this to work.

AndroidManifest.xml

I added the following to my <application> element in main/AndroidManifest.xml:

<meta-data
        android:name="listener"
        android:value="com.myproject.test.runlisteners.CustomRunListener" />

This didn't have any effect. By various means, I have discovered that these lines of code (which are used by AndroidJUnitRunner and RunnerArgs to get custom metadata arguments from the manifest)

InstrumentationInfo instrInfo = pm.getInstrumentationInfo(
    getComponentName(), PackageManager.GET_META_DATA);
Bundle b = instrInfo.metaData;

...return me a null Bundle.

I noticed that the generated debug/AndroidManifest.xml didn't have my metadata tag, and so, as an experiment, I also added it to my androidTest/AndroidManifest.xml file. That looks like this:

<application
    android:name=".BaseApplication">

    <meta-data
        android:name="listener"
        android:value="com.sirius.test.runlisteners.CustomRunListener" />

</application>

...which then appears in the generated debug/AndroidManifest.xml like so:

<application android:name="com.myproject.BaseApplication" >
    <meta-data
        android:name="listener"
        android:value="com.sirius.test.runlisteners.CustomRunListener" />

    <uses-library android:name="android.test.runner" />
</application>

This also didn't have any effect.

Another experiment

I created a custom test runner called CustomAndroidJUnitRunner which extends AndroidJUnitRunner just for the purpose of peaking under the hood a bit. If I do this:

ApplicationInfo ai = packageManager.getApplicationInfo(
    getComponentName().getPackageName(), PackageManager.GET_META_DATA);
Bundle b = ai.metaData;
Object o = b.get("listener");
Log.d(TAG, "listener=" + o.toString());

...logcat will say:

D/CustomAndroidJUnitRunner: listener=com.myproject.test.runlisteners.CustomRunListener

So, ApplicationInfo.metaData has it. Why doesn't InstrumentationInfo.metaData?


Solution

  • Sometimes it's not until you've taken the time to explain everything thoroughly that you finally understand what the problem is. The solution is to add this to the <manifest> element:

    <instrumentation
        android:name="com.myproject.test.runner.CustomAndroidJUnitRunner"
        android:functionalTest="false"
        android:handleProfiling="false"
        android:label="Tests for com.myproject"
        android:targetPackage="com.myproject">
    
        <meta-data
            android:name="listener"
            android:value="com.myproject.test.runlisteners.CustomRunListener" />
    
    </instrumentation>
    

    I just copy-pasted the <instrumentation> element from the generated debug/AndroidManifest.xml file.

    I was a bit thrown off, originally, because both CustomAndroidJUnitRunner and com.myproject were highlighted in red in Android Studio. But everything compiles just fine.

    I hope this helps someone else!