In recent years, the team has received multiple issues regarding Robolectric’s inability to download/resolve the necessary android-all jars when running Robolectric tests in a CI environment. Some examples include:

  1. android-all not downloaded as part of robolectric, or is it a separate dependency?

  2. Robolectric failing because not downloading dependencies in Jenkins when using Artifactory

  3. Flaky SHA mismatch on CI builds when retrieving Maven artifacts since upgrading to 4.10.x

Robolectric downloads the necessary android-all jars using its MavenArtifactFetcher when running Robolectric tests. It does not use any proxies defined by the Gradle build system. In a CI environment, especially in environments used by large companies internally, there are often network restrictions that can cause the aforementioned issues. This article provides some solutions to mitigate these issues as much as possible, including setting a custom proxy for MavenArtifactFetcher, leveraging Robolectric’s offline mode, and manually fetching the necessary android-all jars before running Robolectric tests.

Setting custom proxy for MavenArtifactFetcher

The first solution is setting a custom proxy for MavenArtifactFetcher like the following snippet:

testOptions {
    unitTests {
        includeAndroidResources = true
        returnDefaultValues = true
        isIncludeUnitTestDependencies = true
        all {
            it.systemProperty("robolectric.dependency.repo.url", "https://local-mirror/repo")
            it.systemProperty("robolectric.dependency.repo.id", "local")
            // Username and password only needed when local repository
            // needs account information.
            it.systemProperty("robolectric.dependency.repo.username", "username")
            it.systemProperty("robolectric.dependency.repo.password", "password")
            // Since Robolectric 4.9.1, these are available
            it.systemProperty("robolectric.dependency.proxy.host", System.getenv("ROBOLECTRIC_PROXY_HOST"))
            it.systemProperty("robolectric.dependency.proxy.port", System.getenv("ROBOLECTRIC_PROXY_PORT"))
        }
    }
}

The MavenArtifactFetcher supports the above system properties to leverage a custom Maven repository link, although it needs a username and password. It also supports a custom proxy host and port for internally allowed proxy servers.

robolectric.dependency.repo.url and robolectric.dependency.repo.id are enough for most scenarios. For example, I often set the repository to a custom Chinese popular Maven mirror for my custom projects:

testOptions {
    unitTests {
        includeAndroidResources = true
        returnDefaultValues = true
        isIncludeUnitTestDependencies = true
        all {
            it.systemProperty("robolectric.dependency.repo.url", "https://maven.aliyun.com/repository")
            it.systemProperty("robolectric.dependency.repo.id", "public")
        }
    }
}

Robolectric’s configuration documentation contains a detailed description of these special Robolectric properties, and you can read it for more details.

Leveraging Robolectric’s offline mode

Robolectric supports using android-all jars in a local directory with its offline mode without downloading any android-all jars from the network when running Robolectric tests. We can follow the following snippet to enable Robolectric’s offline mode for the project:

testOptions {
    unitTests {
        includeAndroidResources = true
        returnDefaultValues = true
        isIncludeUnitTestDependencies = true
        all {
            it.systemProperty("robolectric.offline", "true")
            it.systemProperty(
                "robolectric.dependency.dir",
                "${rootDir}/robolectric-jars/preinstrumented"
            )
        }
    }
}

To make it work, we need to download android-all jars into the ${rootDir}/robolectric-jars/preinstrumented directory before running any Robolectric tests. I created a sample project to provide build scripts to download these android-all jars into this preinstrumented directory: robolectric-offline-sample.

plugins {
    `java-library`
}

val versions = listOf(
    "14-robolectric-10818077-i4",
    "13-robolectric-9030017-i4",
    "12.1-robolectric-8229987-i4",
    "12-robolectric-7732740-i4",
    "11-robolectric-6757853-i4",
    "10-robolectric-5803371-i4",
    "9-robolectric-4913185-2-i4",
    "8.1.0-robolectric-4611349-i4",
    "8.0.0_r4-robolectric-r1-i4",
    "7.1.0_r7-robolectric-r1-i4",
    "7.0.0_r1-robolectric-r1-i4",
    "6.0.1_r3-robolectric-r1-i4",
    "5.1.1_r9-robolectric-r2-i4",
    "5.0.2_r3-robolectric-r0-i4",
    "4.4_r1-robolectric-r2-i4"
)

val downloadTasks = versions.map { version ->
    val configurationName = "robolectric$version".replace(".", "_").replace("-", "_")
    val customConfiguration = configurations.create(configurationName) {
        extendsFrom(configurations.implementation.get())
        isCanBeResolved = true
        isCanBeConsumed = false
    }

    dependencies {
        add(configurationName, "org.robolectric:android-all-instrumented:$version")
    }

    val jarFileDirectory = customConfiguration.resolve().map { it.parentFile.absolutePath }
    val allFilesInDirectory = jarFileDirectory.flatMap { fileTree(it).files }

    val downloadTask = tasks.register<Copy>("downloadRobolectricJars$version") {
        from(allFilesInDirectory)
        into("preinstrumented")
    }

    downloadTask
}


val deleteTask = tasks.register<Delete>("deleteRobolectricJars") { delete("preinstrumented") }

tasks.register("downloadAllRobolectricJars") {
    dependsOn(deleteTask)
    dependsOn(downloadTasks)
}

The above build.gradle.kts is just a sample to download necessary android-all jars manually before running Robolectric tests. It’s easy to maintain. Because Robolectric might add a new android-all jar for a new Android version or modify internal logic to update an existing android-all jar’s version, these android-all jars might change across different Robolectric versions. If you store them in a git repository, your git repository might become bigger and bigger. If you like this approach, you can store android-all jars in an external repository like AndroidX.

Robolectric’s configuring documentation contains a detailed description of these special Robolectric properties, and you can read it for details.

Fetching android-all jars manually before running Robolectric tests

If you don’t store android-all jars in your git repository to leverage offline mode, and you don’t want to modify your system properties for Robolectric in your build.gradle.kts, you can try to download android-all jars in a script and download them manually before running any Gradle tasks.

For example, I created a project to do it for myself: robolectric-android-all-fetcher. You can change the Maven mirror to any one you like and run the script to download all android-all jars for a specific Robolectric version.

Conclusion

Most of these issues are caused by a network issue when downloading necessary android-all jars, and we can fix them or ease them by making android-all jars accessible before running Robolectric tests and letting MavenArtifactFetcher use them directly. The above potential solutions are some stable and recommended solutions for developers to try. Hope it can help you.