skip to content

Declaring an integrationTest Suite

Registering an integrationTest suite and getting its source set, task, and configurations created for you. The canonical example an interviewer asks you to write on the spot.

on this pageshow

questions

5

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

open as a page

When you register a suite named integrationTest, what exact names do the derived source set, task, and configurations get, and why does the suite name matter?

level: middleimportance: must knowfreq 50%

basics

~10 s

Everything is named after the suite. Source set: integrationTest (src/integrationTest/...). Task: integrationTest. Configurations: integrationTestImplementation, integrationTestRuntimeOnly, integrationTestCompileOnly, etc. So the suite name is the single key that derives all related artifacts.

open as a page

The built-in `test` suite and a custom integrationTest suite are both JvmTestSuites. How do you reconfigure the built-in one through the same DSL, and how does that relate to registering a new suite?

level: middleimportance: should knowfreq 35%

basics

~20 s

Use getting instead of registering for the existing test suite: val test by getting(JvmTestSuite::class) { useJUnitJupiter() }. A new suite uses registering because it doesn't exist yet; the built-in test already exists, so you fetch and configure it.

open as a page

Contrast declaring an integrationTest JvmTestSuite with the old approach of manually creating a SourceSet and Test task. What boilerplate does the suite eliminate?

level: middleimportance: should knowfreq 55%

basics

~20 s

Manually you create a SourceSet, set its compile/runtime classpaths, and register a Test task pointing at it. Registering a JvmTestSuite does all of that automatically from the suite name, so you write one block instead of a dozen lines.

open as a page

You need every module in a large multi-project build to expose an identical integrationTest suite. How do you roll out the suite declaration so it's consistent and maintainable, and what pitfalls do you watch for?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Put the testing { suites { val integrationTest by registering(JvmTestSuite::class) { ... } } } declaration in a convention plugin (precompiled script plugin in buildSrc or a build-logic module) and apply that plugin to each subproject, so the suite is declared identically everywhere.

open as a page