11

I have a project built with Gradle version 6.4 and JDK 8. I'm trying to use the Gradle plugin for Test Fixtures (java-test-fixtures) but I have some issues with the dependencies.

According to the Gradle page linked above, the project should be structured like this:

core-module
-- src
   -- main
      -- java
   -- test
      -- java
   -- testFixtures
      -- java

While the build.gradle.kts file has the following dependencies section:

dependencies {
    api("com.my.external.project:1.0")
    // ... more API dependencies

    testFixturesCompileOnly(project(":core-module"))
    testFixturesApi("junit:junit:4.12")
    // ... more test dependencies
}

Now, in IntelliJ (the IDE I'm using) classes in the testFixtures/java source folder see the classes in the main/java source folder. So I can add new Java classes under testFixtures/java that have dependencies on those under main. However, I won't be able to import the dependencies from the external library com.my.external.project:1.0. The problem is confirmed when I try to run the Gradle task compileTestFixturesJava.

I can duplicate the entry in the dependencies section; e.g. I can add:

testFixturesImplementationOnly("com.my.external.project:1.0")

But that is not really what I expect to do; especially when I have dozens of dependencies.

I could also define the dependencies in an array and run a for-each over them. Still, this is not the cleanest solution.

Is there a clean solution that will allow the testFixtures module to use the dependencies declared in the main module?

deduper
  • 1,944
  • 9
  • 22
JeanValjean
  • 17,172
  • 23
  • 113
  • 157
  • Any reason you're not just using a multiproject build? – Rob Evans Oct 05 '20 at 09:05
  • This is how it is structured in my company and I can't change it. – JeanValjean Oct 05 '20 at 10:22
  • @JeanValjean Correct me if my understand is wrong -- is the error here that the Gradle task `compileTestFixturesJava` fails without a duplicated entry because it (for whatever reason) is not drawing from the already-specified dependencies declared as `api`/`implementation` under the `dependencies` tag (available to `main`), causing unresolved classes? Also, would you be able to provide a minimalist reproduction repository? – concision Oct 05 '20 at 10:55
  • 1
    I have the same problem, if you know how to solve it, please tell me how – FedorM Oct 07 '20 at 15:46
  • @concision the answer to your first question is yes (if I get you right). For the second, I may not find the time. Let's see. – JeanValjean Oct 07 '20 at 19:04
  • I created a simple project here https://github.com/mricciuti/so-64133013 but could not reproduce your issue: in this example, `testFixture` classes can see `main` dependencies (here :commons-lang3) : if you analyse dependencies you will see that `testFixturesCompileClasspath` inherits from `main` dependencies . maybe you could compare this simple project to yours and check what's different? – M.Ricciuti Oct 07 '20 at 20:19
  • @M.Ricciuti class https://github.com/mricciuti/so-64133013/blob/master/core-module/src/testFixtures/java/com/acme/Simpsons.java in test fixtures is not using JUnit, for instance, nor any code from the main module – JeanValjean Oct 08 '20 at 13:34
  • @JeanValjean 1) "classes in test fixtures is not using JUnit" :indeed, but they are not supposed to! (and if you really need this, just add `junit` dependency in `testFixturesImplementation` configuration. 2) "nor any code from the main module" ==> you're wrong: `Simpsons` class in test Fixture references `Person` class from `main` sourcesSet.. – M.Ricciuti Oct 08 '20 at 19:17
  • @JeanValjean Are you satisfied with the solution already proposed in the comments? Am I correct in thinking it wouldn't be worth anybody else expending any more effort beyond that? To be honest, I'd be surprised if you received any *better* answers — if that's what you're holding out for. – deduper Oct 09 '20 at 14:57
  • @JeanValjean If you are able to provide a minimal reproduction, I can take a detailed look into this. I do not think I would be able to accurately guess your current build configuration. – concision Oct 10 '20 at 01:33
  • @M.Ricciuti why do you say that they are not supposed to use dependencies from the test module? The Gradle page doesn't even says you have to add a dependency from the main module, as you did. For me there is no point in having a testFixtures folder under the another module if the plugin doesn't explicitly inherit from the main module. The fact I also have to import dependencies for testing explicitly for the fixtures folder is stupid, since most likely I will write utilities using JUnit, AssertJ and other testing tools. – JeanValjean Oct 10 '20 at 05:42
  • @deduper no, I'm ok. Disappointed on how poor is the documentation for that plugin and that it is not so flexible as it could. – JeanValjean Oct 10 '20 at 05:43
  • @M.Ricciuti thanks for the POC and comment. You could turn it into an answer, so tha we can shape that fr future devs that will jump in here. – JeanValjean Oct 10 '20 at 05:44
  • @JeanValjean you're right, it makes sense to have test framework dependencies in testFixtures scope (e.g. if you want to setup some shared mock instances using `mockito`), personally I use testFixtures only for setting up domain entity instances for test purpose, but your use case is perfectly valid. in this case, just declare your needed dependencies in `testFixturesImplementation` or `testFixturesApi` scope. regarding dependency from `testFixture` to `main` sourceSet it's automatically set up by the plugin so you don't need to declare it : I did it just because I copied your initial example – M.Ricciuti Oct 10 '20 at 12:08
  • I'll provide soon an answer with an enriched version of my previous POC, to show a valid example based on your last comment. cheers – M.Ricciuti Oct 10 '20 at 12:11
  • 1
    „*…how poor is the documentation for that plugin…*“ — I agree. When I've been stumped by Gradle issues, somebody either [*in their forums*](https://discuss.gradle.org/latest) or [*on their Slack channel*](https://gradle-community.slack.com/) have rescued me. — „*…it is not so flexible as it could…*“ — I think that plugin's goal is to be X-Unit agnostic; to facilitate fixtures meant to be (*re*)used in different projects. One project might use TestNG. Another might use Spock, etc. If JUnit 5's *baked* into the testFixture then it probably wouldn't be as *portable* across different projects. – deduper Oct 10 '20 at 12:47
  • Have you tried this: ```sourceSets { testFixtures { compileClasspath += sourceSets.main.output runtimeClasspath += sourceSets.main.output } }``` (sorry for the bad formatting) – blagerweij Oct 10 '20 at 22:47
  • 1
    „*…Have you tried this: `sourceSets { … }`*“ – @blagerweij — Isn't that already being configured *implicitly* by [*`JavaPlugin.configureSourceSets(JavaPluginConvention, BuildOutputCleanupRegistry)`*](https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaPlugin.java#L329) and [*`JavaTestFixturesPlugin.createImplicitTestFixturesDependencies(Project, JavaPluginConvention)`*](https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaTestFixturesPlugin.java#L78)? – deduper Oct 11 '20 at 12:36

1 Answers1

16

Most important concept in the Gradle java-test-fixtures plugin is stated in their documentation:

[this plugin] will automatically create a testFixtures source set, in which you can write your test fixtures. Test fixtures are configured so that:

  • they can see the main source set classes
  • test sources can see the test fixtures classes

This plugin will indeed create the following dependencies: main <-- testFixtures , and testFixtures <-- test

In your case, testFixtures module should automatically depend on main sources, and also on main dependencies declared in api scope ( com.my.extenal.project:1.0)

See a similar example in a valid sample project here https://github.com/mricciuti/so-64133013 :

  • Simpsons class has access to Person class from main module
  • TestHelpers class has access to main dependencies declared in api configuration

Note that testFixtures will not inherit dependencies from the test module: if you need to use such libraries in this module (eg. JUnit, Mockito, ...) you will need to declare explicit dependency , using testFixturesImplementation or testFixturesApi configuration.

See example in core-module

plugins {
    id ("java-library")
    id ("java-test-fixtures")
}
dependencies {
    // Main dependencies
    //   will be available in "testFixture" as well thanks to "api" configuration
    api("org.apache.commons:commons-lang3:3.9")
    api(project(":utils-module"))

    // Testfixture dependencies
        // ==> mockito lib will be available in testFixture module and also in consumer modules (e.g test)
    testFixturesApi("org.mockito:mockito-core:3.5.13")

    // Test dependencies
       // dependencies specific to the "test" module; not visible from "testFixtures"
    testImplementation("org.junit.jupiter:junit-jupiter-api:5.3.1")
    testRuntimeOnly ("org.junit.jupiter:junit-jupiter-engine:5.3.1")
}
M.Ricciuti
  • 11,070
  • 2
  • 34
  • 54