When you write testImplementation(testFixtures(project(':lib'))), what does Gradle actually resolve under the hood — what variant and capability are involved?
answer
- feature variant + explicit capability
- group:lib-test-fixtures suffix
- testFixturesApiElements / testFixturesRuntimeElements
- requireCapability under the hood
- Gradle Module Metadata carries the capability
basics
~10 stestFixtures() requests a separate capability (group:lib-test-fixtures). Gradle's variant-aware resolution then selects the testFixturesRuntimeElements/testFixturesApiElements consumable variant instead of the module's normal API/runtime variants.
solid answer
~40 sTest fixtures are implemented as a **feature variant** with its own **capability**. Applying `java-test-fixtures` to `:lib` publishes extra consumable variants — `testFixturesApiElements` and `testFixturesRuntimeElements` — each carrying the capability `group:lib-test-fixtures:version` and the standard Java ecosystem attributes (`org.gradle.usage` = java-api/java-runtime, `org.gradle.libraryelements`, `org.gradle.category`). The `testFixtures()` notation in the consumer adds a *requested capability* of `lib-test-fixtures` to the dependency. During resolution Gradle does variant-aware matching: it filters `:lib`'s variants to the one exposing the requested capability, then uses attribute matching to pick API vs runtime depending on whether it's a compile or runtime classpath. Because the metadata records the capability, the exact same mechanism works for **external** modules published with Gradle Module Metadata, not just local subprojects. This is the same machinery behind optional feature variants generally.
code
bash · 4 lines# Inspect which variant/capability got selected
./gradlew :app:dependencyInsight \
--configuration testRuntimeClasspath \
--dependency libgo deeper
Not expected to explain variants; knowing 'it picks a special fixtures jar' is enough.
Should name the two consumable variants and that a capability is involved.
Explain attribute + capability matching, the longhand requireCapability equivalence, and dependencyInsight debugging.
Discuss publishing fixtures with Gradle Module Metadata for external reuse and the governance trade-offs of exposing fixtures as a public capability.
## Variants and capabilities — the foundation Gradle models every published thing as a set of **variants**: a variant is one consumable output (a set of artifacts plus transitive dependencies) tagged with **attributes** (key/value metadata like `org.gradle.usage=java-api`) and **capabilities** (what the variant *provides*, e.g. `com.example:lib:1.0`). Resolution is *variant-aware*: for a given consumer classpath, Gradle computes the requested attributes and capability, then asks each candidate module which of its variants matches. ## What `java-test-fixtures` publishes Applying the plugin to `:lib` adds two outgoing consumable variants: - `testFixturesApiElements` — usage `java-api`, for compile classpaths. - `testFixturesRuntimeElements` — usage `java-runtime`, for runtime classpaths. Both declare an **explicit capability** `group:lib-test-fixtures:version` (note the `-test-fixtures` suffix) in addition to nothing else — they deliberately do *not* carry the module's default capability, so they won't be picked by an ordinary `project(":lib")` request. ## What `testFixtures(...)` requests The `testFixtures()` helper wraps a dependency and attaches a **requested capability**: ```kotlin testImplementation(testFixtures(project(":lib"))) // roughly equivalent to: testImplementation(project(":lib")) { capabilities { requireCapability("com.example:lib-test-fixtures") } } ``` Now resolution looks for the variant of `:lib` whose capability is `com.example:lib-test-fixtures` and whose attributes match the test compile classpath (`java-api`) or test runtime classpath (`java-runtime`). That selects `testFixturesApiElements`/`testFixturesRuntimeElements`. ## Why it generalizes to external modules When `:lib` is published with **Gradle Module Metadata** (`module.json`), the extra capability is recorded. So a consumer in a *different* build can write `testImplementation(testFixtures("com.example:lib:1.0"))` and get the same selection — the capability travels in the metadata. This is why fixtures sharing isn't limited to a single multi-project build. ## Debugging selection `./gradlew :app:dependencyInsight --configuration testRuntimeClasspath --dependency lib` shows which variant was chosen and the capability/attributes that drove the match. If you see the wrong variant, it's usually a missing `testFixtures()` wrapper or a module that wasn't published with fixtures metadata.
- Why does this mechanism also work for an externally published library, not just a local subproject?Because the extra `*-test-fixtures` capability is recorded in the published Gradle Module Metadata, so the consumer's capability request still matches.
- What is `testFixtures(project(':lib'))` roughly equivalent to in longhand?A normal `project(':lib')` dependency with a `capabilities { requireCapability("group:lib-test-fixtures") }` block.
- How would you confirm the right variant was resolved?Run `dependencyInsight` on the test classpath for the `lib` dependency and read the selected variant and its capability.
saying these in an interview costs you the question
- Saying fixtures are 'just a sub-jar with no metadata' — the whole point is the distinct capability + variant attributes.
- Claiming it only works inside one multi-project build; it works for external modules with Gradle Module Metadata too.