Since version 0.0.22 Gretty supports Jacoco instrumentation of web-app projects. The resulting coverage information can be converted to report with the help of standard gradle task JacocoReport. Below are the details.

Code coverage of web-apps consists of two parts:

  • Client-side code coverage covers the code of integration tests (integration tests are typically written with spock + geb or spock + HttpBuilder, but actually it can be any JVM code that connects to your web-app).

  • Server-side code coverage covers the code of the web-app itself.

Client-side code coverage

Client-side code coverage is not related to Gretty at all. It is activated by applying Jacoco plugin to the given project containing integration tests:

apply plugin: 'jacoco'

Side effects of applying Jacoco plugin:

  • Jacoco plugin adds JacocoTaskExtension to each Test task of the given project.

  • Jacoco plugin apppends Jacoco-specific JVM-arguments to each Test task.

  • When Test task runs, Jacoco gathers code coverage to the file "${project.buildDir}/jacoco/${task.name}.exec" ("task.name" is the name of the test task).

  • If you defined JacocoReport task, it creates a report from collected code coverage - html, xml or csv.

Here is the example of client-side code coverage configuration:

apply plugin: 'jacoco'

// ...

test {
  include '**/Test*.*'
  include '**/*Test.*'
  exclude '**/*IT.*'
}

task integrationTest(type: Test, dependsOn: 'test') {
  outputs.upToDateWhen { false }
  finalizedBy { tasks.integrationTestClientReport }
  include '**/*IT.*'
  jacoco {
    append = false
    destinationFile = new File(project.buildDir, 'jacoco/integrationTest_client.exec')
  }
}

task('integrationTestClientReport', type: JacocoReport) {

  executionData project.tasks.integrationTest

  sourceDirectories = project.files(project.sourceSets.test.allSource.srcDirs)
  classDirectories = project.sourceSets.test.output

  def reportDir = project.reporting.file("jacoco/integrationTest_client/html")
  reports {
    html.destination = reportDir
  }
  doLast {
    println "Jacoco report for client created: file://${reportDir.toURI().path}"
  }
}

Server-side code coverage

Server-side code coverage is implemented by Gretty, using Jacoco plugin and task extensions. It is also activated by applying Jacoco plugin:

apply plugin: 'jacoco'

Gretty-specific side effects of applying Jacoco plugin:

  • Jacoco plugin adds JacocoTaskExtension to each app-start task of the given project - to appRun, appRunDebug, …​ and, most importantly, to appBeforeIntegrationTest.

  • By default JacocoTaskExtension is enabled for appBeforeIntegrationTest and disabled for all other app-start tasks. Code coverage is not collected for the tasks with disabled JacocoTaskExtension. You can configure this by setting appRun.jacoco.enabled = true.

  • Jacoco plugin apppends Jacoco-specific JVM-arguments to each app-start task.

  • When app-start task runs, Jacoco gathers code coverage to the file "${project.buildDir}/jacoco/${task.name}.exec" ("task.name" is the name of the app-start task).

  • If you define JacocoReport task, it creates a report from collected code coverage - html, xml or csv.

Here is the example of server-side code coverage configuration:

apply plugin: 'org.gretty'
apply plugin: 'jacoco'

// ...

gretty {
  afterEvaluate {

    tasks.appBeforeIntegrationTest.jacoco {
      append = false
      destinationFile = new File(project.buildDir, 'jacoco/integrationTest_server.exec')
    }

    tasks.appAfterIntegrationTest.finalizedBy tasks.integrationTestServerReport
  }
}

task('integrationTestServerReport', type: JacocoReport) {

  executionData { tasks.appBeforeIntegrationTest.jacoco.destinationFile }

  sourceDirectories = project.files(project.sourceSets.main.allSource.srcDirs)
  classDirectories = project.sourceSets.main.output

  def reportDir = project.reporting.file("jacoco/integrationTest_server/html")
  reports {
    html.destination = reportDir
  }
  doLast {
    println "Jacoco report for server created: file://${reportDir.toURI().path}"
  }
}
Note
You must configure appBeforeIntegrationTest and appAfterIntegrationTest in gretty.afterEvaluate closure: this is because appBeforeIntegrationTest and appAfterIntegrationTest tasks are not yet defined, when the gradle script is processed.
Note
You cannot pass appBeforeIntegrationTest as a parameter to JacocoReport.executionData for the same reason: it is not yet defined. appBeforeIntegrationTest cannot be wrapped with closure, because internally executionData will pass such closure directly to project.files function and the latter has no idea what to do with task. Wrapping appBeforeIntegrationTest.jacoco.destinationFile with closure will work.

Combining custom AppBeforeIntegrationTestTask and AppAfterIntegrationTestTask with jacoco

If you instantiate AppBeforeIntegrationTestTask and AppAfterIntegrationTestTask classes yourself, enabling server-side code coverage looks basically the same:

task('myIntegrationTest', type: Test) {
  // ...
}

task('myBeforeIntegrationTest', type: AppBeforeIntegrationTestTask) {
  jacoco {
    append = false
    destinationFile = new File(project.buildDir, 'jacoco/integrationTest_server.exec')
  }
  integrationTestTask 'myIntegrationTest'
}

task('myAfterIntegrationTest', type: AppAfterIntegrationTestTask) {
  finalizedBy { tasks.integrationTestServerReport }
  integrationTestTask 'myIntegrationTest'
}

task('integrationTestServerReport', type: JacocoReport) {

  executionData tasks.myBeforeIntegrationTest

  sourceDirectories = project.files(project.sourceSets.main.allSource.srcDirs)
  classDirectories = project.sourceSets.main.output

  def reportDir = project.reporting.file("jacoco/integrationTest_server/html")
  reports {
    html.destination = reportDir
  }
  doLast {
    System.out.println "Jacoco report for server created: file://${reportDir.toURI().path}"
  }
}
Note
When instantiating AppBeforeIntegrationTestTask and AppAfterIntegrationTestTask classes yourself, you don’t need to wrap task configuration to gretty.afterEvaluate.
Note
Passing myBeforeIntegrationTest as a parameter to JacocoReport.executionData is now simple: task already exists and Jacoco will correctly extract destinationFile from task’s Jacoco extension.
Tip
When instantiating AppBeforeIntegrationTestTask and AppAfterIntegrationTestTask classes yourself, you can use many nice features, like debugging, by setting up task properties. See more information in AppBeforeIntegrationTestTask and AppAfterIntegrationTestTask class documentation.

Examples

Troubleshooting

If you get exceptions when using combination JDK-8 + Gradle 1.1x + Jacoco + Gretty, you are very likely hitting a problem of compatibility between JDK-8 and earlier versions of Jacoco. See information on how to fix this here.