Multi-project test dependencies with gradle

98,993

Solution 1

Deprecated - For Gradle 5.6 and above use this answer.

In Project B, you just need to add a testCompile dependency:

dependencies {
  ...
  testCompile project(':A').sourceSets.test.output
}

Tested with Gradle 1.7.

Solution 2

Simple way is to add explicit task dependency in ProjectB:

compileTestJava.dependsOn tasks.getByPath(':ProjectA:testClasses')

Difficult (but more clear) way is to create additional artifact configuration for ProjectA:

task myTestsJar(type: Jar) { 
  // pack whatever you need...
}

configurations {
  testArtifacts
}

artifacts {
   testArtifacts myTestsJar
}

and add the testCompile dependency for ProjectB

apply plugin: 'java'
dependencies {
  compile project(':ProjectA')
  testCompile project(path: ':ProjectA', configuration: 'testArtifacts')
}

Solution 3

This is now supported as a first class feature in Gradle. Modules with java or java-library plugins can also include a java-test-fixtures plugin which exposes helper classes and resources to be consumed with testFixtures helper. Benefit of this approach against artifacts and classifiers are:

  • proper dependency management (implementation/api)
  • nice separation from test code (separate source set)
  • no need to filter out test classes to expose only utilities
  • maintained by Gradle

Example

:modul:one

modul/one/build.gradle

plugins {
  id "java-library" // or "java"
  id "java-test-fixtures"
}

modul/one/src/testFixtures/java/com/example/Helper.java

package com.example;
public class Helper {}

:modul:other

modul/other/build.gradle

plugins {
  id "java" // or "java-library"
}
dependencies {
  testImplementation(testFixtures(project(":modul:one")))
}

modul/other/src/test/java/com/example/other/SomeTest.java

package com.example.other;
import com.example.Helper;
public class SomeTest {
  @Test void f() {
    new Helper(); // used from :modul:one's testFixtures
  }
}

Further reading

For more info, see the documentation:
https://docs.gradle.org/current/userguide/java_testing.html#sec:java_test_fixtures

It was added in 5.6:
https://docs.gradle.org/5.6/release-notes.html#test-fixtures-for-java-projects

Solution 4

I've come across this problem myself recently, and man is this a tough issues to find answers for.

The mistake you are making is thinking that a project should export its test elements in the same way that it exports its primary artifacts and dependencies.

What I had a lot more success with personally was making a new project in Gradle. In your example, I would name it

Project A_Test -> src/main/java

I would put into the src/main/java the files that you currently have in Project A/src/test/java. Make any testCompile dependencies of your Project A compile dependencies of Project A_Test.

Then make Project A_Test a testCompile dependency of Project B.

It's not logical when you come at it from the perspective of the author of both projects, but I think it makes a lot of sense when you think about projects like junit and scalatest (and others. Even though those frameworks are testing-related, they are not considered part of the "test" targets within their own frameworks - they produce primary artifacts that other projects just happen to use within their test configuration. You just want to follow that same pattern.

Trying to do the other answers listed here did not work for me personally (using Gradle 1.9), but I've found that the pattern I describe here is a cleaner solution anyway.

Solution 5

I know it's an old question but I just had the same problem and spent some time figuring out what is going on. I'm using Gradle 1.9. All changes should be in ProjectB's build.gradle

To use test classes from ProjectA in tests of ProjectB:

testCompile files(project(':ProjectA').sourceSets.test.output.classesDir)

To make sure that sourceSets property is available for ProjectA:

evaluationDependsOn(':ProjectA')

To make sure test classes from ProjectA are actually there, when you compile ProjectB:

compileTestJava.dependsOn tasks.getByPath(':ProjectA:testClasses')
Share:
98,993

Related videos on Youtube

mathd
Author by

mathd

Updated on February 09, 2022

Comments

  • mathd
    mathd about 2 years

    I have a multi-project configuration and I want to use gradle.

    My projects are like this:

    • Project A

      • -> src/main/java
      • -> src/test/java
    • Project B

      • -> src/main/java (depends on src/main/java on Project A)
      • -> src/test/java (depends on src/test/java on Project A)

    My Project B build.gradle file is like this:

    apply plugin: 'java'
    dependencies {
      compile project(':ProjectA')
    }
    

    The task compileJava work great but the compileTestJava does not compile the test file from Project A.

  • Notre
    Notre almost 13 years
    I tried this (the simple way) and while that makes sure it builds the testClasses, it doesn't add the test path to the CLASSPATH so my ProjectB tests that depend on ProjectA test classes still fail to build.
  • Fesler
    Fesler over 12 years
    Turns out the classes property is deprecated -- use output instead.
  • David Pärsson
    David Pärsson over 11 years
    This does not work in Gradle 1.3 since sourceSets is no longer a public property of a project.
  • Nikita Skvortsov
    Nikita Skvortsov over 10 years
    @dmoebius you have to add testArtifacts configuration like this: configurations { testArtifacts } for more details see this section of Gradle help: gradle.org/docs/current/dsl/…
  • Patrick Bergner
    Patrick Bergner over 10 years
    Keep in mind the above solution requires at least a gradle testClasses before the build structure is actually valid. E.g. the Eclipse plugin won't let you import the project before that. It really is a shame testCompile project(':A') does not work. @DavidPärsson: "Gradle 1.3" contradicts "no longer" since Fesler tested with Gradle 1.7.
  • Peter Lamberg
    Peter Lamberg almost 10 years
    In Gradle 1.8 you may want from sourceSets.test.output and possibly classifier = 'tests' in place of // pack whatever you need... in the answer
  • sfitts
    sfitts almost 10 years
    Confirmed that with Gradle 1.12 using the full solution, with @PeterLamberg suggested additions works as expected. Does not impact import of project into Eclipse.
  • sfitts
    sfitts almost 10 years
    It also creates unnecessary complications in the handling of the project by Eclipse. The solution suggested by @NikitaSkvortsov is preferable.
  • Learn OpenGL ES
    Learn OpenGL ES about 9 years
    The JAR one is the only one that worked for me for an Android test setup, with task jarTest (type: Jar) { from sourceSets.test.output classifier = 'test' } For Android we just have to use "androidTestCompile" instead of "testCompile".
  • koma
    koma almost 9 years
    Yes, opted for this approach at the end of the day.
  • Strinder
    Strinder over 8 years
    How can I prevent that every time the jar builds the tests of it are also executed? That makes it not really useful...
  • Ian
    Ian over 8 years
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From Review
  • demon101
    demon101 over 8 years
    few lines of text has been added
  • demon101
    demon101 over 8 years
    Anyway, information about new gradle plugin has been provided.
  • rahulmohan
    rahulmohan about 8 years
    didn't work for me. Failed with circular dependency: compileTestJava \--- :testClasses \--- :compileTestJava (*)
  • Admin
    Admin about 8 years
    This also worked for me except I had to omit the .classesDir.
  • Czyzby
    Czyzby almost 8 years
    Causes errors after including Eclipse project generated with eclipse task.
  • F. P. Freely
    F. P. Freely over 7 years
    It seems the dependencies are not transitive. With the graph A -> B -> C, C has to add the test output from both A and B.
  • Stefan Oehme
    Stefan Oehme about 7 years
    Don't do this, projects are not supposed to reach into other projects. Instead use Nikita's answer, correctly modelling this as a project dependency.
  • Ryan Newsom
    Ryan Newsom over 6 years
    slight typo, missing the end quote after the project(':A'). This worked for me though, thanks m8
  • Nathan Williams
    Nathan Williams almost 6 years
    This works for me in Gradle 4.7. They now have some docs about the approach at docs.gradle.org/current/dsl/…
  • Robin Green
    Robin Green over 5 years
    The question doesn't mention Android. Can you make your answer agnostic to whether the developer is developing for Android or not, or is it only for Android developers?
  • Vignesh Sundaramoorthy
    Vignesh Sundaramoorthy over 5 years
    @demon101 Not working in Gradle 4.6, getting error Could not get unknown property 'testClasses' for project ':core' of type org.gradle.api.Project.
  • Erik Sillén
    Erik Sillén over 4 years
    This is the best approach! Except I would keep the test code in project A and move only dependencies for both A src/test/java and B src/test/java to A_Test. Then make Project A_Test a testImplementation dependency of both A and B.
  • Erik Sillén
    Erik Sillén over 4 years
    Great solution for the shared mock case!
  • xeruf
    xeruf over 4 years
    Unfortunately (in Gradle 6) the flat include, which was exactly what I wanted, does not work anymore because there is no configuration 'tests' anymore. Using println(configurations.joinToString("\n") { it.name + " - " + it.allDependencies.joinToString() }) (in a kotlin buildscript), I determined which configurations still exist and have dependencies, but for all of these Gradle complained: Selected configuration 'testCompileClasspath' on 'project :sdk' but it can't be used as a project dependency because it isn't intended for consumption by other components.
  • arberg
    arberg about 4 years
    For Android this idea worked beautifully for me, without the hacky feeling stackoverflow.com/a/50869037/197141
  • Beloo
    Beloo about 4 years
    @arberg Yes, seems as a good approach. The only limitation i see is with the @VisibleForTesting lint rules. You won't be able to call such methods from the regular module under not test folder.
  • Albert Vila Calvo
    Albert Vila Calvo almost 4 years
    They are working on supporting this on Android, see issuetracker.google.com/issues/139762443 and issuetracker.google.com/issues/139438142
  • acdcjunior
    acdcjunior over 3 years
    I worked on a project with cycles, so this answer is the only one that worked more or less cleanly. For reference, I put, in the project needing tests from projectExposingTests the snippet (kotlin version): sourceSets { test { java.srcDir(project(":projectExposingTests").file("src/test/‌​java")) } }
  • GhostCat
    GhostCat about 3 years
    Note for IntelliJ users: when using this approach, make sure to name the configuration test. gradle command line works fine with any name, but at least IntelliJ works better with "test" (because it then understands the dependency is actually to the test sources, you dont want the IDEs to "really" depend on the output jars!)
  • GhostCat
    GhostCat about 3 years
    As written above below another answer: just figured that yes, the configuration should really be named test, and nothing else (otherwise IDEs might have difficulties to pick up the dependency to the test sources).
  • ArpitA
    ArpitA about 3 years
    As of now doesn't work for android gradle plugin and kotlin
  • TWiStErRob
    TWiStErRob about 3 years
    Yes, @ArpitA, see the comment before yours. This Q&A is for standard Java Gradle projects.
  • Kartikey Kumar Srivastava
    Kartikey Kumar Srivastava over 2 years
    Worked for me . Thanks