skip to content

How do you share test helper code (fixtures) between two Gradle modules so that one project's tests can reuse fixtures defined in another?

level: juniorimportance: must knowfreq 55%

answer

  1. java-test-fixtures plugin
  2. src/testFixtures/java source set
  3. testImplementation(testFixtures(project(':lib')))
  4. separate consumable variant + capability
  5. producer's own tests get fixtures free

basics

~10 s

Apply 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 s

Gradle'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
kotlin
// 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

for a junior

Know the plugin name, the source-set folder, and the one-line consumer dependency notation.

for a middle

Explain the configurations created (testFixturesImplementation/Api) and that the producer's own tests get fixtures automatically.

for a senior

Explain variant/capability selection and why this beats test-jars or a test-utils module.

for a principal

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.

context