skip to content

Sharing Test Fixtures Across Projects

Exposing a testFixtures source set from one module and consuming it from another, and the variant that carries it. Asked because it is the supported replacement for depending on another module's test jar.

on this pageshow

questions

5

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

open as a page

Inside a testFixtures source set, when should a dependency go in testFixturesApi versus testFixturesImplementation, and what's the visibility difference for consumers?

level: middleimportance: should knowfreq 28%

basics

~10 s

Use testFixturesApi when a fixture's public signature exposes a type from that dependency (it then leaks onto consumers' compile classpath). Use testFixturesImplementation for internal-only deps that consumers shouldn't compile against.

open as a page

A teammate added testImplementation(testFixtures(project(':lib'))) but the consumer's tests can't find the fixture classes at compile time. How do you diagnose and fix it?

level: middleimportance: should knowfreq 24%

basics

~10 s

Check that :lib actually applies java-test-fixtures, that the classes live in src/testFixtures/java, and that any types the consumer references are declared testFixturesApi (not testFixturesImplementation). Then use dependencyInsight to confirm the right variant resolved.

open as a page

When you write testImplementation(testFixtures(project(':lib'))), what does Gradle actually resolve under the hood — what variant and capability are involved?

level: seniorimportance: should knowfreq 30%

basics

~10 s

testFixtures() 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.

open as a page

Why are test fixtures preferred over the older approach of sharing a 'test-jar' or pointing another module at src/test directly?

level: seniorimportance: nice to knowfreq 18%

basics

~10 s

Test fixtures give a first-class, variant-aware, separately-published surface with its own dependencies, instead of brittle hacks like classifier test-jars or wiring another module's sourceSets.test.output, which leak full test code and lose proper metadata.

open as a page