Test fixtures are helper classes and resources upon which tests are built. For instance:
- Initializing a model class with test data
- Helper methods for json manipulation and evaluation
- widely used mocking constructions
Test fixtures are often created by extracting code that is used in several test classes. It becomes interesting if this is to be done across several (sub-)projects.
The Problem: Neither test, nor production code
In this example we have an application with three subprojects: app
, domain
and exporters
:
The tests in the domain
project require concrete test data. These are built in a separate TestDataFactory
class.
This factory would also provide a useful basis to test the json exporter in the exporters
project.
Unfortunately, a dependency cannot be directly defined. 1 This limitation is a deliberate design decision in Gradle,
as tests are usually only executed and do not provide artifacts for other projects.
The solution: The java-test-fixtures plugin
With Gradle 5.6, test fixtures are natively supported:
For this purpose, the corresponding Gradle plugin is integrated into the subproject that provides the test fixtures:
domain/build.gradle
:
plugins {
id 'java-library'
id 'java-test-fixtures'
}
dependencies {
// ...
testFixturesImplementation 'com.google.guava:guava:31.1-jre'
}
Gradle then automatically creates and configures a new SourceSet testFixtures
, in addition to main
and test
.
The default directory structure is as follows:
︙ ├── domain │ ├── build.gradle │ └── src │ ├── main │ │ └── java │ │ └── de/cronn │ │ └── CustomerEntity.java │ ├── test │ │ └── java │ │ └── de/cronn │ │ └── ModelTest.java │ └── testFixtures │ └── java │ └── de/cronn │ └── TestDataFactory.java └── exporters ├── build.gradle ︙
Test fixture dependencies can be specified using the testFixturesImplementation
and testFixturesApi
configurations. These behave like the implementation
and api
configurations of the java-library
plugin.
Including the test fixtures as a dependency
In the last step, the dependency in the exporters
project is declared:
exporters/build.gradle
:
dependencies {
// ...
testImplementation testFixtures(project(':domain'))
}
With this setup, the ExportTest
class from the exporters
project can now access the TestDataFactory
from the domain
project.
Separate projects
Sharing the test fixtures works not only between Gradle subprojects, but also with separate projects.
In such cases, dependencies are established through published artifacts. With the java-test-fixtures plugin no further configuration is necessary. By default, another artifact is already created and published:
mygroup ├── myproject │ └── 1.0 │ ├── myproject-1.0-plain.jar │ ├── myproject-1.0-sources.jar │ ├── myproject-1.0-test-fixtures.jar │ ├── myproject-1.0.module │ └── myproject-1.0.pom
With Gradle’s own maven-publish plugin, additional meta-information is stored in the *.module
file. 2
This allows the dependencies to be declared in the second project in the expected format:
dependencies {
// ...
testImplementation testFixtures("mygroup.myproject:1.0")
}
IDE Support
Since the Gradle plugin is based on existing techniques (SourceSet
), the IDE support is good.
When searching for text and symbols, the IDEs usually provide filtering capabilities. You can limit the search results to test or production code. The test fixtures are classified differently:
- For Eclipse, test fixtures are (correctly) part of the test code.
- In IntelliJ, the test fixtures are considered production code. If you want to change the assignment during filtering, you can set up your own scopes.
Links
-
In Gradle up to version 5.5.1 several Workarounds were used: (1) a separate project for the test fixtures (
domain-test-common
); (2) Get Gradle to export the compiled classes of thetest
-SourceSet as artifact (via appropriate code in thebuild.gradle
). ↩ -
To keep naming simple and consistent, we recommend setting
rootProject.name
andartifactId
to the same value. ↩