for a unit test that has a compileOnly dependency, how can i avoid redeclaring the dependency for availability at runtime?

I am currently using Gradle to build a project, and it has a dependency on a third party component that I need at compile time, but which will be provided at runtime. In maven I would declare this dependency as provided, and in Gradle I declare it as below:

  compileOnly group: 'org.apache.spark', name: 'spark-sql_2.11', version: '2.4.0.cloudera1'

The fact that I am depending on the above spark artifact is not really germane to the question (I provide this info to make the example more concrete.)

Now, suppose I want to write some unit tests for my app (or library as the case may be). When using Gradle, the only way I figured out how to do this is clunky: I redeclare the dependency as a testCompile dependency, and my test is able to run:

  compileOnly group: 'org.apache.spark', name: 'spark-sql_2.11', version: '2.4.0.cloudera1'
  testCompile group: 'org.apache.spark', name: 'spark-sql_2.11', version: '2.4.0.cloudera1'

I really don't like the repetition and clutter of declaring my dependency twice, and I'm wondering if there is any better way to do this in Gradle ?

Conclusion

The answer from Mike put me on to the solution that I chose, which was to put this at the top level gradle file of my multi-project build.

subprojects {

  sourceSets {
    test.compileClasspath += configurations.compileOnly
    test.runtimeClasspath += configurations.compileOnly
  }
}

This is actually a very standard way to declare Gradle dependencies. There are many cases in which compileOnly dependencies can be used, and not all of these require the dependency to be provided at runtime (unlike Maven's provided scope implies).

This is detailed further in Gradle's initial announcement for compile-only dependencies (https://blog.gradle.org/introducing-compile-only-dependencies):

Compile-only dependencies address a number of use cases, including:

  • Dependencies required at compile time but never required at runtime, such as source-only annotations or annotation processors;
  • Dependencies required at compile time but required at runtime only when using certain features, a.k.a. optional dependencies;
  • Dependencies whose API is required at compile time but whose implementation is to be provided by a consuming library, application or runtime environment.

What you have is the idiomatic way of declaring a compile-only dependency for your main sources which is also a runtime dependency for your test sources (although, technically, recent Gradle versions would suggest you replace the deprecated testCompile configuration with the testImplementation configuration).

However, one of the beautiful things about Gradle is that it is highly customizable. The built-in configurations, such as compileOnly and testImplementation can be modified. If you wish to change the built-in behavior, you can modify the testImplementation configuration to extend the compileOnly configuration, which will cause all compileOnly dependencies to be included when testImplementation is resolved:

// give test dependencies access to compileOnly dependencies to emulate providedCompile
configurations {
    testImplementation.extendsFrom compileOnly
}

Source: https://discuss.gradle.org/t/compileonly-dependencies-are-not-available-in-tests/15366/8