# Reporting code coverage with JaCoCo Sample

version 7.3.1

- Groovy DSL

- Kotlin DSL

| You can open this sample inside an IDE using the IntelliJ native importer or Eclipse Buildship.

This sample shows how to test Java projects with JaCoCo in Gradle.

To collect code coverage across multiple subprojects, we need to setup two aspects. First, the projects that run the tests and collect the code coverage data:

buildSrc/src/main/groovy/myproject.java-conventions.gradle

    plugins {
        id 'java'
        id 'jacoco'
    }

    version = '1.0.2'
    group = 'org.gradle.sample'

    repositories {
        mavenCentral()
    }

    tasks.named("test") {
        useJUnitPlatform()
    }

    dependencies {
        testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1'
    }

    // Do not generate reports for individual projects
    tasks.named("jacocoTestReport") {
        enabled = false
    }

    // Share sources folder with other projects for aggregated JaCoCo reports
    configurations.create('transitiveSourcesElements') {
        visible = false
        canBeResolved = false
        canBeConsumed = true
        extendsFrom(configurations.implementation)
        attributes {
            attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
            attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION))
            attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, 'source-folders'))
        }
        sourceSets.main.java.srcDirs.forEach {
            outgoing.artifact(it)
        }
    }

    // Share the coverage data to be aggregated for the whole product
    configurations.create('coverageDataElements') {
        visible = false
        canBeResolved = false
        canBeConsumed = true
        extendsFrom(configurations.implementation)
        attributes {
            attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
            attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION))
            attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, 'jacoco-coverage-data'))
        }
        // This will cause the test task to run if the coverage data is requested by the aggregation task
        outgoing.artifact(tasks.named("test").map { task ->
            task.extensions.getByType(JacocoTaskExtension).destinationFile
        })
    }

buildSrc/src/main/kotlin/myproject.java-conventions.gradle.kts

    plugins {
        java
        jacoco
    }

    version = "1.0.2"
    group = "org.gradle.sample"

    repositories {
        mavenCentral()
    }

    tasks.test {
        useJUnitPlatform()
    }

    dependencies {
        testImplementation("org.junit.jupiter:junit-jupiter:5.7.1")
    }

    // Do not generate reports for individual projects
    tasks.jacocoTestReport {
        enabled = false
    }

    // Share sources folder with other projects for aggregated JaCoCo reports
    configurations.create("transitiveSourcesElements") {
        isVisible = false
        isCanBeResolved = false
        isCanBeConsumed = true
        extendsFrom(configurations.implementation.get())
        attributes {
            attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
            attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION))
            attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named("source-folders"))
        }
        sourceSets.main.get().java.srcDirs.forEach {
            outgoing.artifact(it)
        }
    }

    // Share the coverage data to be aggregated for the whole product
    configurations.create("coverageDataElements") {
        isVisible = false
        isCanBeResolved = false
        isCanBeConsumed = true
        extendsFrom(configurations.implementation.get())
        attributes {
            attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
            attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION))
            attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named("jacoco-coverage-data"))
        }
        // This will cause the test task to run if the coverage data is requested by the aggregation task
        outgoing.artifact(tasks.test.map { task ->
            task.extensions.getByType<JacocoTaskExtension>().destinationFile!!
        })
    }

Here we do the setup in a convention plugin called `myproject.java-conventions` which we apply to all our application and library projects. Here, the build is setup to run tests using JUnit5 and we apply the `jacoco` plugin to collect the code coverage. With the plugin applied, it automatically attaches itself to the `test` task to collect the code coverage. Please refer to the JaCoCo chapter for more details on the plugin configuration.

Second, to generate a report spanning all our tested projects, we introduce and additional project `code-coverage-report` that collects source files and coverage data from all its dependent projects. This is setup in a second convention plugin plugin called `myproject.jacoco-aggregation` which provides a task called `codeCoverageReport` to generate the aggregated report.

buildSrc/src/main/groovy/myproject.jacoco-aggregation.gradle

    plugins {
        id 'java'
        id 'jacoco'
    }

    repositories {
        mavenCentral()
    }

    // A resolvable configuration to collect source code
    def sourcesPath = configurations.create("sourcesPath") {
        visible = false
        canBeResolved = true
        canBeConsumed = false
        extendsFrom(configurations.implementation)
        attributes {
            attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
            attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION))
            attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, 'source-folders'))
        }
    }

    // A resolvable configuration to collect JaCoCo coverage data
    def coverageDataPath = configurations.create("coverageDataPath") {
        visible = false
        canBeResolved = true
        canBeConsumed = false
        extendsFrom(configurations.implementation)
        attributes {
            attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
            attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION))
            attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, 'jacoco-coverage-data'))
        }
    }

    // Task to gather code coverage from multiple subprojects
    def codeCoverageReport = tasks.register('codeCoverageReport', JacocoReport) {
        additionalClassDirs(configurations.runtimeClasspath)
        additionalSourceDirs(sourcesPath.incoming.artifactView { lenient(true) }.files)
        executionData(coverageDataPath.incoming.artifactView { lenient(true) }.files.filter { it.exists() })

        reports {
            // xml is usually used to integrate code coverage with
            // other tools like SonarQube, Coveralls or Codecov
            xml.required = true

            // HTML reports can be used to see code coverage
            // without any external tools
            html.required = true
        }
    }

    // Make JaCoCo report generation part of the 'check' lifecycle phase
    tasks.named("check") {
        dependsOn(codeCoverageReport)
    }

buildSrc/src/main/kotlin/myproject.jacoco-aggregation.gradle.kts

    plugins {
        java
        jacoco
    }

    repositories {
        mavenCentral()
    }

    // A resolvable configuration to collect source code
    val sourcesPath: Configuration by configurations.creating {
        isVisible = false
        isCanBeResolved = true
        isCanBeConsumed = false
        extendsFrom(configurations.implementation.get())
        attributes {
            attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
            attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION))
            attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named("source-folders"))
        }
    }

    // A resolvable configuration to collect JaCoCo coverage data
    val coverageDataPath: Configuration by configurations.creating {
        isVisible = false
        isCanBeResolved = true
        isCanBeConsumed = false
        extendsFrom(configurations.implementation.get())
        attributes {
            attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
            attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION))
            attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named("jacoco-coverage-data"))
        }
    }

    // Task to gather code coverage from multiple subprojects
    val codeCoverageReport by tasks.registering(JacocoReport::class) {
        additionalClassDirs(configurations.runtimeClasspath.get())
        additionalSourceDirs(sourcesPath.incoming.artifactView { lenient(true) }.files)
        executionData(coverageDataPath.incoming.artifactView { lenient(true) }.files.filter { it.exists() })

        reports {
            // xml is usually used to integrate code coverage with
            // other tools like SonarQube, Coveralls or Codecov
            xml.required.set(true)

            // HTML reports can be used to see code coverage
            // without any external tools
            html.required.set(true)
        }
    }

    // Make JaCoCo report generation part of the 'check' lifecycle phase
    tasks.check {
        dependsOn(codeCoverageReport)
    }

Running the tests and generate the report:

    $ ./gradlew codeCoverageReport

    BUILD SUCCESSFUL
    11 actionable tasks: 11 executed

For more information, see Testing in Java project chapter.