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:
Create a custom class that extends AndroidJUnitRunner in the
androidTest folder.
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.
Related
I am building a Gradle Plugin that should generate both flavorDimensions and productFlavors on Android. Everything works fine when this plugin is being applied directly to the Android module. My goal however is to apply this plugin to another module within the same project as the Android module. Currently this does not work due to a AgpDslLockedException being thrown during Gradle Sync.
My plugin is implemented as follows:
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
// Traverse whole project tree to find Android project
project.rootProject.subprojects.forEach { subproject ->
val android = subproject.extensions.findByName("android") as? AppExtension ?: return#forEach
// throws AgpDslLockedException when being applied to other module than android
android.flavorDimensions.add("dimension")
android.productFlavors.create("flavor") { it.dimension = "dimension" }
}
}
A minimal client for the Gradle Plugin could be structured as follows:
- android
- build.gradle
- other
- build.gradle
- settings.gradle
// android/build.gradle
plugins {
id 'com.android.application'
// MyPlugin would work if being applied here
}
android {
// Neither flavorDimensions nor productFlavors are declared as they should be generated by MyPlugin
}
// other/build.gradle
plugins {
// MyPlugin does not work due to AgpDslLockedException being thrown
}
// settings.gradle
include(":android")
include(":other")
The AgpDslLockedException:
It is too late to modify flavorDimensions.
It has already been read to configure this project.
Consider either moving this call to be during evaluation, or using the variant API.
Am I missing something or is there another way to generate flavorDimensions and productFlavors from a third-party build.gradle file?
So far I have tried adding the flavorDimension and creating the productFlavor on project.beforeEvaluate/afterEvaluate but to not avail. Maybe there is a lifecycle method I am missing.
Thanks in advance and have a nice day!
I'm trying to set up a sample android studio project in Github which integrates also its CI connected to SonarQube.
I'm having problems with sonarQube due to it says there is no test coverage applied. However there is, in kotlin and with Juni5, but there is no way to set sonar.tests for recognizing it.
this it the sonar-project.properties
test are run fine either locally and in the CI when a branch of the project is build.
Also I've added this
Gradle has been written with Kotlin-DSL.
if you are using gradle there is no need to define the sources and the tests in the sonar-project.properties. The gradle sonarqube task will pick them up automatically based on your sourcesets.
What i think you are missing is a tool to generate the coverage, sonarqube will not generate coverage data for you. SonarQube utilizes in Java eg. JaCoCo so you also need to apply a plugin for that.
so your build gradle (behold this is groovy dsl, but i will provide a link to one in kotlin) will look something like the following code snippet. This will generate everything automatically
plugins {
id 'java'
id 'jacoco'
id 'eclipse' // optional (to generate Eclipse project files)
id 'idea' // optional (to generate IntelliJ IDEA project files)
id "org.sonarqube" version "2.8"
}
repositories {
jcenter()
}
dependencies {
testImplementation('org.junit.jupiter:junit-jupiter:5.6.0')
}
test {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
}
}
sonarqube {
properties {
/*
UPDATE SECTION START
Please fill in your data
*/
property "sonar.projectName", "SonarCloud Github Actions with gradle"
property "sonar.projectKey", "aepfli_SonarCloud-GitHubActions-Gradle-example"
property "sonar.organization", "aepfli"
/*
UPDATE SECTION END
*/
property "sonar.host.url", "https://sonarcloud.io"
}
}
jacocoTestReport {
reports {
xml.enabled = true
}
}
Alternatively if you are looking for an example in kotlin DSL, i can recommend this one from JUnit Pioneer.
I am still not sure how good the support for kotlin is with JaCoCo. if there is a different tool like JaCoCo in the Kotlin world, you can also try to generate and XML report and provide this xml report via property sonar.coverage.jacoco.xmlReportPaths base on Sonarqube doc
Is there a way to leverage the new functions withSourcesJar() and withJavadocJar() for Android library projects? Currently when I try to use it I get:
> SourceSet with name 'main' not found.
With the latest Gradle version 6.0.1, there seems to be no way to use these new methods in Android library projects. Here’s why I believe that’s the case:
The two methods withJavadocJar() and withSourcesJar() on the java extension have the default main source set hardcoded, see here and here.
There are two methods with the same names (withJavadocJar() and withSourcesJar()) which can be used in feature variant declarations. However, it seems that Android Gradle builds don’t use feature variants, i.e., these methods can’t be used either.
The documentation states, that these come from the JavaPluginExtension - but not from Android DSL. So they can only be used in conjunction with apply plugin: "java" or apply plugin: "java-library", but not with apply plugin: "com.android.application", apply plugin: "com.android.library" and alike. The name of these tasks also suggest that it's common Java (*.jar) and not Android (*.aar). On Android, this would only make sense for a *.jar library, which uses pure Java features, but no Android features at all (which is limited in functionality).
In short, apply plugin: "java-library" would permit accessing these.
With Gradle 7.2 I "recreated" withJavadocJar and withSourcesJar using Gradle Kotlin DSL:
val sourceFiles = android.sourceSets.getByName("main").java.getSourceFiles()
tasks.register<Javadoc>("withJavadoc") {
isFailOnError = false
dependsOn(tasks.named("compileDebugSources"), tasks.named("compileReleaseSources"))
// add Android runtime classpath
android.bootClasspath.forEach { classpath += project.fileTree(it) }
// add classpath for all dependencies
android.libraryVariants.forEach { variant ->
variant.javaCompileProvider.get().classpath.files.forEach { file ->
classpath += project.fileTree(file)
}
}
source = sourceFiles
}
tasks.register<Jar>("withJavadocJar") {
archiveClassifier.set("javadoc")
dependsOn(tasks.named("withJavadoc"))
val destination = tasks.named<Javadoc>("withJavadoc").get().destinationDir
from(destination)
}
tasks.register<Jar>("withSourcesJar") {
archiveClassifier.set("sources")
from(sourceFiles)
}
Put this into your gradle.build.kts file and then run ./gradlew withJavadocJar and ./gradlew withSourcesJar (or use the tasks to create artifacts for publishing).
android.sourceSets.getByName("main").java.getSourceFiles() is the Android specific part to retrieve the "main" source files.
I` am trying to create some extra Dagger 2 modules for android instrumental tests. And Dagger 2 files are generated successfully, but my Android Studio IDE can't see it. I tried to use android-apt plug-in, gradle-apt-plagin and native android annotation processor. Nevertheless, nothing worked. However gradle 2 file for debug or prod application working fine.
For test module and component I use some custom sours folder:
def commonTestDir = 'src/commonTest/java'
test {
java.srcDir commonTestDir
}
androidTest {
java.srcDir commonTestDir
}
I use all of compile/apt, testCompile/testApt, androidTestCompile/androidTestApt for daggger 2 declaration in build.gradle file.
p.s. I`am sorry for my bad english...
I have the android application and I want to use Espresso framework to create test automation tools and tests. But I don't want to create something in my app and want separate module with Espresso that will start my app and test it using Espresso. I use Android Studio. So.. Have you any ideas how to solve this problem?
You can use a library module specialised for instrumentation tests. Basically just create a library module but use a different plugin:
apply plugin: 'com.android.test' // A plugin used for test-only-modules
Also you need to changep your android part in your build.gradle file in your test module a little:
android {
[...]
defaultConfig {
[...]
testApplicationId "com.yourcompany.yourapp.test"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
[...]
// Set the target app project.
// The module specified here should contain the
// production code test should run against.
targetProjectPath ':app'
}
Then add your app and all your espresso libraries etc. in your test module's dependencies:
dependencies {
implementation project(':app')
// All your test dependencies etc.
implementation "androidx.test.espresso:espresso-core:$espresso_version"
}
Using a gradle sync and run tests you can then execute your tests locally. You'll also get all your normal gradle tasks for that module which you can use to assemble your test APK etc. if you need it to test externally e.g. in Firebase Test Labs.