skip to content

How do you declare a separate integrationTest test suite in a Gradle build using the JVM Test Suite plugin, and what does that one declaration give you for free?

level: juniorimportance: must knowfreq 70%

answer

  1. testing { suites { } } container
  2. registering(JvmTestSuite::class)
  3. auto source set + task + configurations
  4. named by the suite, e.g. integrationTest
  5. wire into check manually

basics

~10 s

Inside the testing.suites block, register a suite with val integrationTest by registering(JvmTestSuite::class). Gradle then auto-creates the integrationTest source set, the integrationTest task, and its dependency configurations — no manual source-set wiring needed.

solid answer

~40 s

The `java` (or `java-library`) plugin applies the JVM Test Suite plugin, exposing the `testing { suites { } }` container. Inside it you write `val integrationTest by registering(JvmTestSuite::class) { useJUnitJupiter() }`. From that single line Gradle materialises three things automatically: a `src/integrationTest/java` (and `kotlin`/`resources`) **source set**, an `integrationTest` **Test task** that runs only that source set, and the matching **dependency configurations** (`integrationTestImplementation`, `integrationTestRuntimeOnly`, etc.). This replaces the old boilerplate where you hand-created a source set, wired its compile/runtime classpaths, and registered a `Test` task pointing at it. You usually also add `tasks.named("check") { dependsOn(integrationTest) }` so it joins the lifecycle, since suites other than `test` are not wired into `check` by default.

code

kotlin · 11 lines
kotlin
testing {
    suites {
        val integrationTest by registering(JvmTestSuite::class) {
            useJUnitJupiter()
        }
    }
}

tasks.named("check") {
    dependsOn(testing.suites.named("integrationTest"))
}

go deeper

for a junior

Recall that you register a suite in testing { suites { } } and Gradle creates the source set, task, and configurations from the suite name.

for a middle

Name all three auto-created artifacts and remember to wire the suite into check manually; know registering is the lazy Kotlin DSL form.

for a senior

Explain the configuration-avoidance angle of registering, the naming convention that drives derivation, and why this replaces hand-rolled source sets.

for a principal

Frame suites as the standard, conventional way to add test source roots across a multi-module build so every project gets uniform layout and tasks without bespoke build logic.

## What problem JVM Test Suites solve Before Gradle 7.3, adding an integration-test source root meant manual boilerplate: create a `SourceSet`, point its `compileClasspath`/`runtimeClasspath` at `main` output and the `test` configurations, then register a `Test` task and tell it where the test classes live. Easy to get subtly wrong. The **JVM Test Suite plugin** (auto-applied by `java`/`java-library`) replaces that with a declarative DSL. A *test suite* is a logical group of tests sharing a source set, dependencies, and a runtime — `test` is the built-in suite; you add more. ## The single declaration ```kotlin testing { suites { val integrationTest by registering(JvmTestSuite::class) { useJUnitJupiter() } } } ``` The `suites` container is a `NamedDomainObjectContainer<JvmTestSuite>`. Using Kotlin's `registering` delegate registers the suite **lazily** (created only when needed). The name of the suite — `integrationTest` — drives everything that follows. ## What gets auto-created From that one registration Gradle derives, by naming convention: - **Source set** `integrationTest` → `src/integrationTest/java`, `src/integrationTest/resources` (and `kotlin` if the Kotlin plugin is applied). - **Test task** `integrationTest` of type `Test`, configured to run the suite's source set, with its own report and results directories. - **Dependency configurations** `integrationTestImplementation`, `integrationTestCompileOnly`, `integrationTestRuntimeOnly`, `integrationTestAnnotationProcessor`, plus the resolvable/consumable classpath configurations behind them. ## Wiring into the lifecycle The built-in `test` suite runs as part of `check`. **Custom suites do not** unless you opt in: ```kotlin tasks.named("check") { dependsOn(testing.suites.named("integrationTest")) } ``` (Per-target lifecycle wiring is its own topic; here the key fact is the suite alone does not join `check`.) ## Why `registering` not `register` `registering` is the Kotlin DSL delegated-property form that returns a `NamedDomainObjectProvider` and defers creation. It mirrors `tasks.register` laziness, so the suite participates in configuration avoidance instead of being eagerly realised. Net effect: one declarative block gives you an isolated compile + run environment for integration tests with zero manual source-set plumbing.

  • After declaring the suite, where do you put the integration-test source files?
    Under the auto-created source set: `src/integrationTest/java` (or `kotlin`), with resources in `src/integrationTest/resources`. The directory name matches the suite name.
  • Why isn't your integrationTest task running when you call `./gradlew check`?
    Custom suites aren't wired into `check` automatically. You must add `dependsOn` from `check` to the suite (or its task) yourself.

Declaring a suite is like naming a new tenant for a building: you say the name once and the property manager (Gradle) automatically allocates the rooms (source set), the mailbox (task), and the utility accounts (configurations) under that name.

saying these in an interview costs you the question

  • Claiming the integrationTest task runs as part of `check` automatically — only the built-in `test` suite does.
  • Saying you still must manually create the source set and configurations — the whole point is that registering the suite does it for you.

context