By default jacocoTestReport doesn't run when you run tests. How do you wire it so the coverage report is produced right after the test task?
answer
- finalizedBy = runs even on test failure
- dependsOn = correct input ordering
- use both directions
- test -> finalizedBy report
- report -> dependsOn test
basics
~10 sMake the report run with tests: tasks.test { finalizedBy(tasks.jacocoTestReport) }, and have the report depend on tests with tasks.jacocoTestReport { dependsOn(tasks.test) }. Then ./gradlew test produces the report automatically.
solid answer
~50 sThere are two complementary wirings, and the right answer uses both directions for different reasons. First, `jacocoTestReport` should **depend on** `test` (`dependsOn(tasks.test)`) because the report's input is the execution data the test run produces — if someone invokes the report directly, tests must run first. Second, `test` should **finalize** with the report (`finalizedBy(tasks.jacocoTestReport)`) so that running `./gradlew test` automatically generates coverage afterward, even if tests fail (a finalizer still runs, so you get partial coverage of what executed). `finalizedBy` is preferred over `dependsOn` for the test→report direction because it guarantees ordering without making the report a prerequisite of test, and it runs after failures. You can also fold the report into the gate by making `check` depend on it. Avoid only using `dependsOn` from report to test if you also want `./gradlew test` alone to emit reports.
code
kotlin · 11 linestasks.test {
finalizedBy(tasks.jacocoTestReport)
}
tasks.jacocoTestReport {
dependsOn(tasks.test)
reports {
xml.required.set(true)
html.required.set(true)
}
}go deeper
Know you must run jacocoTestReport (or wire it) because test alone won't generate the report.
Distinguish finalizedBy vs dependsOn and why both directions are useful.
Explain failure-tolerance of finalizers and folding the report into check; keep references lazy.
Bake the wiring into a shared convention plugin so coverage is produced uniformly across all modules and CI never forgets a step.
## The two relationships There are two distinct task relationships at play, and confusing them is a classic interview trap. ### 1. `dependsOn` — correctness of inputs `jacocoTestReport` reads `build/jacoco/test.exec`. That file only exists after `test` runs. So if a user runs `./gradlew jacocoTestReport` directly, tests must execute first: ```kotlin tasks.jacocoTestReport { dependsOn(tasks.test) } ``` Without this, invoking the report alone could produce an empty/stale report. ### 2. `finalizedBy` — convenience and failure-tolerance You usually want `./gradlew test` to *also* emit coverage. Making the report a finalizer of `test` does that: ```kotlin tasks.test { finalizedBy(tasks.jacocoTestReport) } ``` `finalizedBy` has a key property: **a finalizer runs even if the finalized task fails.** So when some tests fail, you still get a report covering whatever executed — invaluable for debugging. If you instead used `dependsOn` in this direction, a test failure would abort before the report ran. ## Why not just one? - Only `finalizedBy(test → report)`: running the report *directly* wouldn't guarantee tests ran first (no input dependency). - Only `dependsOn(report → test)`: running `./gradlew test` alone wouldn't produce a report, and the report wouldn't run after a failure. Using both covers every invocation path. ## Folding into the gate Many teams also want coverage as part of `check`: ```kotlin tasks.check { dependsOn(tasks.jacocoTestReport) } ``` ## Putting it together ```kotlin tasks.test { finalizedBy(tasks.jacocoTestReport) } tasks.jacocoTestReport { dependsOn(tasks.test) reports { xml.required.set(true); html.required.set(true) } } ``` ## Mechanics note These wirings use lazy task references (`tasks.test`, `tasks.jacocoTestReport`) so the tasks aren't eagerly realized — keeping configuration fast and configuration-cache friendly.
- Why use `finalizedBy` for the test→report link instead of `dependsOn`?A finalizer runs even when the finalized task fails, so you still get a coverage report for whatever tests executed before the failure. `dependsOn` in that direction would abort the report on a test failure.
- If you only add `jacocoTestReport.dependsOn(test)`, does `./gradlew test` produce a report?No. That only ensures tests run before the report when the report is invoked; running `test` by itself won't trigger the report. You need `test.finalizedBy(jacocoTestReport)` for that.
- How do you make coverage part of the standard `check` gate?`tasks.check { dependsOn(tasks.jacocoTestReport) }`, so `./gradlew check` generates the report alongside other verification.
saying these in an interview costs you the question
- Saying `dependsOn` alone makes `./gradlew test` emit a report — it doesn't.
- Claiming a finalizer is skipped when the finalized task fails — it actually still runs.