Gradle Testing Tasks and Test Reports: A Complete Guide

Gradle Testing Tasks and Test Reports: A Complete Guide

Gradle's test task orchestrates JVM test execution with fine-grained control over filtering, parallelism, JVM configuration, and reporting. Understanding how to configure it properly saves significant time in both development and CI.

Basic Test Configuration

The test task is automatically applied with the java plugin:

// build.gradle
plugins {
    id 'java'
}

test {
    useJUnitPlatform()  // for JUnit 5 / Spock 2.x
    // useJUnit()       // for JUnit 4
    // useTestNG()      // for TestNG
}

JVM Arguments and System Properties

test {
    useJUnitPlatform()

    // JVM args
    jvmArgs '-Xmx512m', '-XX:+EnableDynamicAgentLoading'

    // System properties accessible via System.getProperty()
    systemProperty 'spring.profiles.active', 'test'
    systemProperty 'app.base.url', 'http://localhost:8080'

    // Environment variables
    environment 'DATABASE_URL', 'jdbc:h2:mem:test'

    // Max heap for test JVM
    maxHeapSize = "512m"
}

Test Filtering

Run specific tests without modifying code:

# Run all tests in a class
./gradlew <span class="hljs-built_in">test --tests <span class="hljs-string">"com.example.OrderServiceTest"

<span class="hljs-comment"># Run a specific method
./gradlew <span class="hljs-built_in">test --tests <span class="hljs-string">"com.example.OrderServiceTest.should create order"

<span class="hljs-comment"># Wildcard matching
./gradlew <span class="hljs-built_in">test --tests <span class="hljs-string">"*.OrderServiceTest"
./gradlew <span class="hljs-built_in">test --tests <span class="hljs-string">"com.example.*"

<span class="hljs-comment"># Multiple patterns
./gradlew <span class="hljs-built_in">test --tests <span class="hljs-string">"*.OrderServiceTest" --tests <span class="hljs-string">"*.PaymentServiceTest"

Filter in the build file for permanent exclusions:

test {
    filter {
        includeTestsMatching "com.example.unit.*"
        excludeTestsMatching "com.example.slow.*"
    }
}

Parallel Test Execution

test {
    useJUnitPlatform()

    // Run in parallel using multiple workers
    maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1

    // Fork a new JVM every N tests (isolate state)
    forkEvery = 100
}

maxParallelForks spawns multiple test JVMs. forkEvery limits how many tests run in a single JVM process before forking a fresh one — useful when tests leak static state.

Custom Test Source Sets

Separate unit and integration tests:

// build.gradle
sourceSets {
    integrationTest {
        groovy {
            srcDir 'src/integrationTest/groovy'
        }
        resources {
            srcDir 'src/integrationTest/resources'
        }
        compileClasspath += main.output + test.output
        runtimeClasspath += main.output + test.output
    }
}

configurations {
    integrationTestImplementation.extendsFrom testImplementation
    integrationTestRuntimeOnly.extendsFrom testRuntimeOnly
}

task integrationTest(type: Test) {
    description = 'Runs integration tests.'
    group = 'verification'

    testClassesDirs = sourceSets.integrationTest.output.classesDirs
    classpath = sourceSets.integrationTest.runtimeClasspath

    useJUnitPlatform()

    shouldRunAfter test
}

check.dependsOn integrationTest

Run: ./gradlew integrationTest

Test Logging

Control what appears in the console:

test {
    testLogging {
        events "passed", "skipped", "failed"
        exceptionFormat "full"
        showExceptions true
        showCauses true
        showStackTraces true

        // Show test output on failure only
        showStandardStreams false

        afterSuite { desc, result ->
            if (!desc.parent) {
                println "\nTest Results: ${result.resultType} " +
                    "(${result.testCount} tests, " +
                    "${result.successfulTestCount} passed, " +
                    "${result.failedTestCount} failed, " +
                    "${result.skippedTestCount} skipped)"
            }
        }
    }
}

HTML Test Reports

Gradle generates HTML reports automatically at build/reports/tests/test/index.html. Customize the location:

test {
    reports {
        html {
            outputLocation = file("${buildDir}/reports/unit-tests")
        }
        junitXml {
            outputLocation = file("${buildDir}/test-results/unit")
        }
    }
}

JUnit XML output (build/test-results/) is what CI systems (Jenkins, GitHub Actions) parse for test result visualization.

Test Retry

Add the retry plugin for flaky test handling:

plugins {
    id 'org.gradle.test-retry' version '1.5.8'
}

test {
    retry {
        maxRetries = 2
        maxFailures = 5
        filter {
            includeClasses.add("*.*IntegrationTest")
        }
    }
}

Skipping Tests

# Skip all tests
./gradlew build -x <span class="hljs-built_in">test

<span class="hljs-comment"># Skip specific task
./gradlew integrationTest -x <span class="hljs-built_in">test

In the build file:

test {
    onlyIf { !project.hasProperty('skipTests') }
}

Run: ./gradlew test -PskipTests

Test Caching

Gradle's build cache skips test execution if inputs haven't changed. Enable in gradle.properties:

org.gradle.caching=true

Tests must be deterministic (no timestamps, random seeds) for caching to be reliable. Use @RepeatedTest with a fixed seed in JUnit 5, or @Seed in Spock, when tests involve randomness.

Aggregated Reports with Test Aggregation Plugin

For multi-module projects, aggregate all results into one report:

// build.gradle (root project)
plugins {
    id 'test-report-aggregation'
}

dependencies {
    testReportAggregation project(':module-a')
    testReportAggregation project(':module-b')
}

reporting {
    reports {
        testAggregateTestReport(AggregateTestReport) {
            testType = TestSuiteType.UNIT_TEST
        }
    }
}

Run: ./gradlew testAggregateTestReport

CI Best Practices

test {
    useJUnitPlatform()

    // Always publish XML for CI parsing
    reports {
        junitXml.required = true
    }

    // Don't stop the build on first test failure in CI
    ignoreFailures = System.getenv("CI") == "true"

    // Parallel on CI (usually more cores available)
    maxParallelForks = System.getenv("CI") ? 4 : 1

    // Fail fast in local dev
    failFast = !System.getenv("CI")
}

GitHub Actions example:

- name: Run tests
  run: ./gradlew test --continue

- name: Publish test results
  uses: EnricoMi/publish-unit-test-result-action@v2
  if: always()
  with:
    files: build/test-results/**/*.xml

Useful Commands

./gradlew test                          <span class="hljs-comment"># run tests
./gradlew <span class="hljs-built_in">test --rerun-tasks            <span class="hljs-comment"># force re-run even if up-to-date
./gradlew <span class="hljs-built_in">test --info                   <span class="hljs-comment"># verbose output
./gradlew <span class="hljs-built_in">test --debug                  <span class="hljs-comment"># very verbose
./gradlew <span class="hljs-built_in">test --<span class="hljs-built_in">continue               <span class="hljs-comment"># don't stop on first failure
./gradlew dependencies --configuration testRuntimeClasspath  <span class="hljs-comment"># debug classpath

Gradle's test infrastructure is one of its strongest features. With proper configuration, you get fast feedback cycles locally, reliable CI runs, and readable reports — all without external tooling.

Read more