It took a while but finally, I’ve got an opportunity to use Junit Jupiter (Junit5) in an Android project. And it wasn’t easy as I expected, but you know, with tests it’s never easy 🙂
So lets start with gradle configuration:
Project level build.gradle
dependencies {
// A Gradle plugin that allows for the execution of JUnit 5 tests in
// Android environments
classpath "de.mannodermaus.gradle.plugins:android-junit5:1.6.2.0"
// The JaCoCo plugin provides code coverage metrics
classpath "org.jacoco:org.jacoco.core:0.8.6"
}
More information about the plugins:
- https://github.com/mannodermaus/android-junit5/blob/master/README.md
- https://docs.gradle.org/current/userguide/jacoco_plugin.html
Module level build.gradle
plugins {
id 'de.mannodermaus.android-junit5'
id 'jacoco'
}
// Attached below
apply from: 'jacoco.gradle'
android {
defaultConfig {
// Allows instrumentation tests with JUnit5
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// Connect JUnit 5 to the runner
testInstrumentationRunnerArgument("runnerBuilder", "de.mannodermaus.junit5.AndroidJUnit5Builder")
}
// Java 8 is required
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
// JUnit 5 will bundle in files with identical paths, exclude them
packagingOptions {
exclude("META-INF/LICENSE*")
}
}
jacoco.gradle
// Do this if you have Robolectric tests
tasks.withType(Test) {
jacoco.includeNoLocationClasses = true
}
// Set Jacoco version and location for the report
jacoco {
toolVersion = "0.8.6"
reportsDir = file("$buildDir/JacocoReportDir")
}
android {
buildTypes {
debug {
testCoverageEnabled = true
}
}
testOptions {
animationsDisabled true
unitTests {
includeAndroidResources = true
returnDefaultValues = true
}
}
}
// Example for Jacoco report excluding (Explained below)
project.ext.filter = [
'**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Test*.*',
'android/**/*.*',
'**/*$ViewInjector*.*',
'**/*Mock*.*',
'**/*$Lambda$*.*',
'**/*Companion*.*',
'**/*Module.*',
'**/*MembersInjector*.*',
'**/*_Factory*.*',
'**/*_Provide*Factory*.*',
'**/*Extensions*.*',
'**/*$Result.*',
'**/*$Result$*.*',
'**/*inlined*.*',
'**/*body*',
'**/*observeViewModel*',
'**/*$1*',
]
// This task generate the Jacoco report
task jacocoTestReporting(type: JacocoReport, dependsOn: ['testDebugUnitTest']) {
group = "Reporting"
reports {
xml.enabled = true
html.enabled = true
}
getClassDirectories().setFrom(fileTree(dir: "$buildDir/tmp/kotlin-classes/debug", excludes: filter))
getSourceDirectories().setFrom(files([android.sourceSets.main.java.srcDirs, "src/main/java"]))
getExecutionData().setFrom(files("$buildDir/jacoco/testDebugUnitTest.exec"))
}
// This task generate the Jacoco report and opens it in your default browser
task getCoverage(type: Exec, dependsOn: 'jacocoTestReporting') {
group = "Reporting"
commandLine "cmd", "/c", "start $buildDir/JacocoReportDir/jacocoTestReporting/html/index.html"
}
Jacoco exclude rules
A PatternFilterable represents some file container which Ant-style include and exclude patterns or specs can be applied to.
Patterns may include:
‘*’ to match any number of characters
‘?’ to match any single character
‘**’ to match any number of directories or files Either ‘/’ or ‘\’ may be used in a pattern to separate directories. Patterns ending with ‘/’ or ‘\’ will have ‘‘ automatically appended.
Examples:
all files ending with ‘jsp’ (including subdirectories)
*/.jsp
all files beginning with ‘template_’ in the level1/level2 directory
level1/level2/template_*
all files (including subdirectories) beneath src/main/webapp
src/main/webapp/
all files beneath any .svn directory (including subdirectories) under src/main/java
src/main/java//.svn/
Source: https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/util/PatternFilterable.html
Dependencies
Junit5
testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.0")
// (Optional) If you need "Parameterized Tests"
testImplementation("org.junit.jupiter:junit-jupiter-params:5.7.0")
// (Optional) If you also have JUnit 4-based tests
testImplementation "junit:junit:4.13"
testRuntimeOnly "org.junit.vintage:junit-vintage-engine:5.7.0"
Mockk and Koin dependencies
// Mocking
testImplementation "io.mockk:mockk:1.10.2"
// Koin test
testImplementation "org.koin:koin-test:2.2.0-rc-4"
testImplementation "org.koin:koin-test-junit5:2.2.0-rc-4"
Instrumentaion tests dependencies
// Core library
androidTestImplementation 'androidx.test:core:1.3.0'
// Mocking
androidTestImplementation "io.mockk:mockk-android:1.10.2"
// AndroidJUnitRunner and JUnit Rules
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test:rules:1.3.0'
// Jupiter API
androidTestImplementation "org.junit.jupiter:junit-jupiter-api:5.7.0"
// The instrumentation test companion libraries
androidTestImplementation("de.mannodermaus.junit5:android-test-core:1.2.0")
androidTestRuntimeOnly("de.mannodermaus.junit5:android-test-runner:1.2.0")
In my next articles I’ll cover following topics:
- Junit 5 test with mocks, using Mockk and Koin libraries.
- Junit 5 instrumentation, tests using Mockk and Koin libraries.