Android Junit5 tests, Gradle configuration with Mockk, Koin, and Jacoco

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:
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.


Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.