skip to content

How do you declare dependencies that are scoped to just one test suite, and how does that differ from adding them to the global testImplementation configuration?

level: middleimportance: should knowfreq 40%

answer

  1. suite.dependencies { implementation(...) }
  2. implementation(project()) = production output
  3. per-suite configs: integrationTestImplementation
  4. testImplementation only feeds the test suite
  5. use*() adds framework dep to suite implementation

basics

~10 s

Use the suite's dependencies { } block: integrationTest { dependencies { implementation(project()); implementation("org.testcontainers:junit-jupiter:1.19.0") } }. Those deps go only on that suite's classpath, not on test.

solid answer

~40 s

Each `JvmTestSuite` exposes its own `dependencies { }` block with suite-scoped configurations: `implementation`, `compileOnly`, `runtimeOnly`, `annotationProcessor`. Dependencies declared there land only on *that* suite's compile/runtime classpath. This is different from the legacy `dependencies { testImplementation(...) }` at the project level, which only feeds the built-in `test` suite. So for an `integrationTest` suite you write `integrationTest { dependencies { implementation("org.testcontainers:junit-jupiter:1.19.0") } }` and Testcontainers is available to integration tests but not unit tests. A key idiom is `implementation(project())` (note the empty parentheses): it adds the current project's *production* output to the suite. The suite-level `use*()` helpers (e.g. `useJUnitJupiter()`) also push the framework dependency into this same `implementation` configuration automatically.

code

kotlin · 12 lines
kotlin
testing {
    suites {
        val integrationTest by registering(JvmTestSuite::class) {
            useJUnitJupiter()
            dependencies {
                implementation(project())
                implementation("org.testcontainers:junit-jupiter:1.19.0")
                runtimeOnly("org.postgresql:postgresql:42.7.0")
            }
        }
    }
}

go deeper

for a junior

Recall that a suite has its own dependencies { } block and that implementation(project()) brings in production code.

for a middle

Explain the per-suite configurations, how they differ from project-level testImplementation, and that use*() helpers auto-add the framework dependency.

for a senior

Discuss classpath isolation benefits, sharing common deps via version catalogs/platforms, and avoiding cross-suite leakage.

for a principal

Set conventions for dependency hygiene across modules — centralizing test-stack versions and keeping integration-only libraries off unit-test classpaths org-wide.

## Suite-scoped dependencies The JVM Test Suite model gives every suite an isolated dependency scope. Inside a suite you open a `dependencies { }` block whose methods mirror the familiar configurations but apply **only to that suite's source set**: ```kotlin testing { suites { val integrationTest by registering(JvmTestSuite::class) { useJUnitJupiter("5.10.0") dependencies { implementation(project()) // production code implementation("org.testcontainers:junit-jupiter:1.19.0") runtimeOnly("org.postgresql:postgresql:42.7.0") compileOnly("org.projectlombok:lombok:1.18.30") } } } } ``` Available methods: `implementation`, `compileOnly`, `runtimeOnly`, `annotationProcessor`, plus platform/enforcedPlatform helpers. They map to per-suite configurations like `integrationTestImplementation`, `integrationTestRuntimeOnly`, etc. ## `project()` vs `project(":x")` vs `project.dependencies` The single most idiomatic and easily-confused line is: ```kotlin implementation(project()) ``` - `project()` with **no arguments** is a method on the suite-dependencies DSL that resolves to **the current project's production output** (its `main` source set + its api/implementation deps). It is how a custom suite gains access to the classes it is testing. (The built-in `test` suite gets this implicitly; custom suites do **not**, so you must add it.) - `project(":other")` references a *different* subproject. - Don't confuse this with the top-level `dependencies { }` block, which is a different DSL entirely. ## Contrast with project-level testImplementation The legacy approach: ```kotlin dependencies { testImplementation("org.assertj:assertj-core:3.25.0") } ``` adds to the `testImplementation` configuration, which feeds **only the built-in `test` suite**. It does nothing for a custom `integrationTest` suite. To share a dependency across suites you either declare it in each suite's `dependencies { }` block, or add it to the project-level configuration that the suite extends (e.g. `integrationTestImplementation`), or factor common deps into a platform / version-catalog bundle. ## Why suite-scoped deps matter - **Isolation / leaner classpaths**: unit tests don't drag in Testcontainers, Docker clients, or Selenium just because integration tests need them. Smaller classpaths compile and run faster and avoid accidental coupling. - **Self-describing suites**: everything a suite needs is declared next to the suite. - **Correctness**: keeps integration-only libraries out of the unit-test compile classpath, so a unit test can't accidentally depend on an integration-only type. ## Interaction with use*() helpers Calling `useJUnitJupiter()`, `useSpock()`, `useTestNG()`, etc. on the suite both selects the runner and **adds the matching test framework dependency to the suite's own `implementation`** — so you don't hand-add `junit-jupiter` after calling `useJUnitJupiter()`.

  • What does implementation(project()) with empty parentheses mean inside a suite?
    It adds the current project's production (main) output and its dependencies to the suite, giving the suite access to the code it tests. Custom suites need this explicitly; the built-in test suite gets it automatically.
  • Why won't dependencies { testImplementation(...) } show up in a custom integrationTest suite?
    testImplementation feeds only the built-in test suite's source set. A custom suite has its own configuration (integrationTestImplementation), so project-level testImplementation deps are not on its classpath.

saying these in an interview costs you the question

  • Claiming testImplementation deps are automatically available to every suite.
  • Confusing the suite dependencies DSL's project() with the top-level dependencies block.
  • Manually re-adding junit-jupiter after already calling useJUnitJupiter().

context