Search code examples
androidgradleandroid-testing

Shared Custom AndroidJUnitRunner for multiple Android Library modules


Description

I'm trying to create a custom AndroidJUnitRunner that can be reused by multiple Android Library modules in my project for my Hilt Android tests so I don't have to keep creating a new one in each module.

Project Setup

I have a custom Android Library gradle config that is responsible for configuring everything related to Android.

android-library-config.gradle:

apply plugin: "com.android.library"
apply plugin: "kotlin-android"

android {
    // android related configuration
}

// additional configuration that should apply to ALL android library modules

Then, every Android Library module applies this gradle config instead of defining its own configuration:

some-feature-module/build.gradle:

apply from: "$rootProject.projectDir/android-library-config.gradle"

android {
    // additional setup if needed
}

dependencies {
    implementation projects.someLibraryInThisProject
}

Adding Hilt Android UI Tests to an Android Library module

Following the Hilt testing documentation for instrumented tests:

To use the Hilt test application in instrumented tests, you need to configure a new test runner. This makes Hilt work for all of the instrumented tests in your project. Perform the following steps:

  1. Create a custom class that extends AndroidJUnitRunner in the androidTest folder.
  2. Override the newApplication function and pass in the name of the generated Hilt test application.

With the following example in the androidTest directory:

// A custom runner to set up the instrumented application class for tests.
class CustomTestRunner : AndroidJUnitRunner() {

    override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
        return super.newApplication(cl, HiltTestApplication::class.java.name, context)
    }
}

Then, we need to configure the new test runner in our gradle file:

android {
    defaultConfig {
        // Replace com.example.android.dagger with your class path.
        testInstrumentationRunner "com.example.android.dagger.CustomTestRunner"
    }
}

However, I don't want to have to create a new CustomTestRunner for every Android Library module in my project. Instead, I want to create one and reference it only in my android-library-config.gradle so that it gets applied to each module that applies that config.

What I've tried

I've tried introducing a new Android Library module, :library:ui-testing:common, and adding the custom test runner in its androidTest directory:

library
  └─ ui-testing
       └─ common
            └─ src
                 └─ androidTest
                      └─ java
                           └─ com.uitesting.common.CustomTestRunner

And then, updated my android-library-config.gradle to reference this new test runner:

apply plugin: "com.android.library"
apply plugin: "kotlin-android"

android {
    defaultConfig {
        // Replace com.example.android.dagger with your class path.
        testInstrumentationRunner "com.uitesting.common.CustomTestRunner"
    }
}

dependencies {
    implementation projects.library.uiTesting.common
}

Since :some-feature-module/build.gradle applies android-library-config.gradle I expect my Instrumented tests defined in this module to use this new custom test runner. Instead, my tests just don't run. I get the following output:

Starting 0 tests on Pixel 3 - 10

I've also tried having :some-feature-module directly depend on :library:ui-testing:common and adding the following to :some-feature-module/build.gradle:

android {
    defaultConfig {
        // Replace com.example.android.dagger with your class path.
        testInstrumentationRunner "com.uitesting.common.CustomTestRunner"
    }
}

and same result.

I'm wondering if this is even possible since I haven't found any examples of this in the official Android Instrumented test documentation.


Solution

  • Seems like in order to make the custom instrumentation runner available for the build script to pick up is to place the runner in main/ instead of androidTest of :ui-testing:common.

    Then your feature module that is going to use the test runner should depend on :ui-testing:common directly as you described. (implementation(project("...")))

    You can also see here that AndroidJunitRunner is placed in java/... and not javatests/....