How do you share test helper code (fixtures) between two Gradle modules so that one project's tests can reuse fixtures defined in another?
answer
- java-test-fixtures plugin
- src/testFixtures/java source set
- testImplementation(testFixtures(project(':lib')))
- separate consumable variant + capability
- producer's own tests get fixtures free
basics
~10 sApply the java-test-fixtures plugin to the producing module, put shared helpers in src/testFixtures/java, then in the consumer declare testImplementation(testFixtures(project(":lib"))).
solid answer
~40 sGradle's built-in `java-test-fixtures` plugin lets a module publish reusable test helper code without leaking it into its main artifact. Applying it creates a `testFixtures` source set under `src/testFixtures/java` (or `/kotlin`) and a corresponding `testFixturesImplementation`/`testFixturesApi` configuration for its dependencies. Code there can see the module's `main` classes. A consuming module declares a dependency on the *fixtures variant* with the `testFixtures()` notation: `testImplementation(testFixtures(project(":lib")))`. That tells Gradle to resolve the `testFixturesCjarElements` variant of `:lib` rather than its normal API/runtime variants, so the consumer's test compile/runtime classpath gains the fixture classes. The producer's own tests also automatically get the fixtures on their classpath. This avoids the old anti-pattern of a separate `:test-utils` project or `test-jar` hacks.
code
kotlin · 14 lines// lib/build.gradle.kts (producer)
plugins {
`java-library`
`java-test-fixtures`
}
dependencies {
testFixturesImplementation("org.assertj:assertj-core:3.25.3")
}
// app/build.gradle.kts (consumer)
dependencies {
testImplementation(testFixtures(project(":lib")))
}go deeper
Know the plugin name, the source-set folder, and the one-line consumer dependency notation.
Explain the configurations created (testFixturesImplementation/Api) and that the producer's own tests get fixtures automatically.
Explain variant/capability selection and why this beats test-jars or a test-utils module.
Frame fixture sharing as a build-architecture concern: governance of what counts as a fixture vs production code, publishing fixtures for external consumers, monorepo conventions.
## The problem In a multi-project build you often write helper code for tests — builders, fake implementations, assertion utilities, sample data factories — that you'd like to reuse from *another* module's tests. Putting it in `src/main` ships it to production; putting it in `src/test` makes it invisible to other modules because the `test` source set isn't published as a consumable variant. ## The `java-test-fixtures` plugin Gradle ships a built-in plugin that solves this cleanly: ```kotlin plugins { `java-library` `java-test-fixtures` } ``` Applying it does three things in the **producing** module: 1. Creates a new source set `testFixtures` rooted at `src/testFixtures/java` (and `src/testFixtures/resources`). 2. Wires that source set so it can compile against the module's own `main` output. 3. Registers dependency configurations `testFixturesImplementation`, `testFixturesApi`, `testFixturesRuntimeOnly`, etc., plus **consumable outgoing variants** (`testFixturesApiElements`, `testFixturesRuntimeElements`) that expose a `*-test-fixtures.jar`. The producing module's own `test` source set automatically gets the fixtures on its classpath, so you can use them locally too. ## Consuming the fixtures In another module: ```kotlin dependencies { testImplementation(testFixtures(project(":lib"))) } ``` The `testFixtures(...)` wrapper is the key. Without it, `project(":lib")` resolves the normal API/runtime variant. With it, Gradle attaches a **capability** request for `lib-test-fixtures` and uses attribute matching to select the test-fixtures variant. The consumer's test compile and runtime classpath then includes the fixture classes (and their transitive `testFixturesApi` dependencies). ## How variant selection works Under the hood the fixtures are modelled as a *feature variant* carrying a distinct **capability** (`group:lib-test-fixtures`). Gradle's variant-aware resolution matches the requested capability and the `org.gradle.usage`/`org.gradle.libraryelements` attributes to pick `testFixturesRuntimeElements`. This is why `testFixtures()` works for both `project(...)` (composite/multi-project) and external `module(...)` coordinates that were published with fixtures metadata (Gradle Module Metadata records the extra capability). ## Why not a separate test-utils project? A dedicated `:test-utils` module also works but has costs: it can't easily reach into the producer's `internal`/`main` types, it pollutes the dependency graph for production, and it needs its own publishing. Test fixtures keep helpers next to the code they exercise while keeping them out of the production jar.
- Where does the fixtures source code physically live?In the producing module under `src/testFixtures/java` (and `src/testFixtures/resources`), a source set created by the plugin.
- Do you need to do anything special for the producing module's own tests to use the fixtures?No — applying `java-test-fixtures` automatically puts the fixtures on that module's `test` classpath.
saying these in an interview costs you the question
- Claiming you must create a separate `:test-utils` project — that's the older workaround the plugin replaces.
- Forgetting the `testFixtures(...)` wrapper and just writing `testImplementation(project(":lib"))`, which resolves the wrong variant.