How do I add a new sourceset to Gradle?
Asked Answered
K

10

121

I want to add integration tests to my Gradle build (Version 1.0). They should run separately from my normal tests because they require a webapp to be deployed to localhost (they test that webapp). The tests should be able to use classes defined in my main source set. How do I make this happen?

Krutz answered 20/7, 2012 at 14:30 Comment(0)
K
138

Update for 2021:

A lot has changed in 8ish years. Gradle continues to be a great tool. Now there's a whole section in the docs dedicated to configuring Integration Tests. I recommend you read the docs now.

Original Answer:

This took me a while to figure out and the online resources weren't great. So I wanted to document my solution.

This is a simple gradle build script that has an intTest source set in addition to the main and test source sets:

apply plugin: "java"

sourceSets {
    // Note that just declaring this sourceset creates two configurations.
    intTest {
        java {
            compileClasspath += main.output
            runtimeClasspath += main.output
        }
    }
}

configurations {
    intTestCompile.extendsFrom testCompile
    intTestRuntime.extendsFrom testRuntime
}

task intTest(type:Test){
    description = "Run integration tests (located in src/intTest/...)."
    testClassesDir = project.sourceSets.intTest.output.classesDir
    classpath = project.sourceSets.intTest.runtimeClasspath
}
Krutz answered 20/7, 2012 at 14:30 Comment(7)
You'll still need to declare and configure an integration test task. In terms of documentation, there is a java/withIntegrationTests sample in the full Gradle distribution.Combination
Thanks @PeterNiederwieser I've corrected my sample build script.Krutz
@PeterNiederwieser Thanks - could you please link it up? I also find this exact situation severely lacking in docs: It is all well and good defining a new sourceSet, but no info about "hooking this into" the actual compile, jar, test and whatnot targets - as this example does (except for adding into the jar, or making a new jar, out of that sourceSet).Naturopathy
On line 6, I get "Cannot resolve symbol 'java'" when using IntelliJ. Any thoughts on why?Electrocardiogram
I would expect the java symbol to be defined in line 1. Is that present? If you're using a newer version of Gradle they may have change the implementation of sourceSets. If so, you'll need to check the appropriate docs.Krutz
@Naturopathy github.com/gradle/gradle/tree/master/subprojects/docs/src/…Viceregent
I see only one difference to the doc docs.gradle.org/4.10.2/userguide/… at the configurations block, any reason why? @Viceregent interesting enough this samples doesn't match the documentation ;(Ripe
B
35

Here is how I achieved this without using configurations{ }.

apply plugin: 'java'

sourceCompatibility = JavaVersion.VERSION_1_6

sourceSets {
    integrationTest {
        java {
            srcDir 'src/integrationtest/java'
        }
        resources {
            srcDir 'src/integrationtest/resources'
        }
        compileClasspath += sourceSets.main.runtimeClasspath
    }
}

task integrationTest(type: Test) {
    description = "Runs Integration Tests"
    testClassesDir = sourceSets.integrationTest.output.classesDir
    classpath += sourceSets.integrationTest.runtimeClasspath
}

Tested using: Gradle 1.4 and Gradle 1.6

Borges answered 29/5, 2013 at 16:34 Comment(2)
while java { srcDir 'src/integrationtest/java' } resources { srcDir 'src/integrationtest/resources' } is not relevant since it just redeclares src/<sourceSetName>/... to src/integrationtest/...: here: change the capital T to a lower tRipe
Beware of this approach. compileClasspath += sourceSets.main.runtimeClasspath is combining two sets of files. There is no usual conflict resolution for dependencies. You can end up with two versions of the same library. Extending configurations will help with that.Ephemeron
F
25

This was once written for Gradle 2.x / 3.x in 2016 and is far outdated!! Please have a look at the documented solutions in Gradle 4 and up


To sum up both old answers (get best and minimum viable of both worlds):

some warm words first:

  1. first, we need to define the sourceSet:

    sourceSets {
        integrationTest
    }
    
  2. next we expand the sourceSet from test, therefor we use the test.runtimeClasspath (which includes all dependenciess from test AND test itself) as classpath for the derived sourceSet:

    sourceSets {
        integrationTest {
            compileClasspath += sourceSets.test.runtimeClasspath
            runtimeClasspath += sourceSets.test.runtimeClasspath // ***)
        }
    }
    
    • note) somehow this redeclaration / extend for sourceSets.integrationTest.runtimeClasspath is needed, but should be irrelevant since runtimeClasspath always expands output + runtimeSourceSet, don't get it
  3. we define a dedicated task for just running integration tests:

    task integrationTest(type: Test) {
    }
    
  4. Configure the integrationTest test classes and classpaths use. The defaults from the java plugin use the test sourceSet

    task integrationTest(type: Test) {
        testClassesDir = sourceSets.integrationTest.output.classesDir
        classpath = sourceSets.integrationTest.runtimeClasspath
    }
    
  5. (optional) auto run after test

    integrationTest.dependsOn test
    
  6. (optional) add dependency from check (so it always runs when build or check are executed)

    tasks.check.dependsOn(tasks.integrationTest)
    
  7. (optional) add java,resources to the sourceSet to support auto-detection and create these "partials" in your IDE. i.e. IntelliJ IDEA will auto create sourceSet directories java and resources for each set if it doesn't exist:

    sourceSets {
         integrationTest {
             java
             resources
         }
    }
    

tl;dr

apply plugin: 'java'

// apply the runtimeClasspath from "test" sourceSet to the new one
// to include any needed assets: test, main, test-dependencies and main-dependencies
sourceSets {
    integrationTest {
        // not necessary but nice for IDEa's
        java
        resources

        compileClasspath += sourceSets.test.runtimeClasspath
        // somehow this redeclaration is needed, but should be irrelevant
        // since runtimeClasspath always expands compileClasspath
        runtimeClasspath += sourceSets.test.runtimeClasspath
    }
}

// define custom test task for running integration tests
task integrationTest(type: Test) {
    testClassesDir = sourceSets.integrationTest.output.classesDir
    classpath = sourceSets.integrationTest.runtimeClasspath
}
tasks.integrationTest.dependsOn(tasks.test)

referring to:

Unfortunatly, the example code on github.com/gradle/gradle/subprojects/docs/src/samples/java/customizedLayout/build.gradle or …/gradle/…/withIntegrationTests/build.gradle seems not to handle this or has a different / more complex / for me no clearer solution anyway!

Fachini answered 17/6, 2016 at 12:49 Comment(3)
(!) as it turns out, the single use of sourceSet enhancements without configurations or output results in a make error in idea after initally opening a project. the build dependency (here: test) for the new "module" (here: integrationTest) isn't available upon first compileTestJavaRipe
classesDir was migrated to classesDirs on gradle 5Hermon
thanks for the hint @deFreitas, I marked the answer as outdatedRipe
P
10

The nebula-facet plugin eliminates the boilerplate:

apply plugin: 'nebula.facet'
facets {
    integrationTest {
        parentSourceSet = 'test'
    }
}

For integration tests specifically, even this is done for you, just apply:

apply plugin: 'nebula.integtest'

The Gradle plugin portal links for each are:

  1. nebula.facet
  2. nebula.integtest
Pasticcio answered 9/8, 2016 at 18:3 Comment(0)
P
7

If you're using

To get IntelliJ to recognize custom sourceset as test sources root:

plugin {
    idea
}

idea {
    module {
        testSourceDirs = testSourceDirs + sourceSets["intTest"].allJava.srcDirs
        testResourceDirs = testResourceDirs + sourceSets["intTest"].resources.srcDirs
    }
}
Pectase answered 14/2, 2019 at 17:2 Comment(0)
R
2

Here's what works for me as of Gradle 4.0.

sourceSets {
  integrationTest {
    compileClasspath += sourceSets.test.compileClasspath
    runtimeClasspath += sourceSets.test.runtimeClasspath
  }
}

task integrationTest(type: Test) {
  description = "Runs the integration tests."
  group = 'verification'
  testClassesDirs = sourceSets.integrationTest.output.classesDirs
  classpath = sourceSets.integrationTest.runtimeClasspath
}

As of version 4.0, Gradle now uses separate classes directories for each language in a source set. So if your build script uses sourceSets.integrationTest.output.classesDir, you'll see the following deprecation warning.

Gradle now uses separate output directories for each JVM language, but this build assumes a single directory for all classes from a source set. This behaviour has been deprecated and is scheduled to be removed in Gradle 5.0

To get rid of this warning, just switch to sourceSets.integrationTest.output.classesDirs instead. For more information, see the Gradle 4.0 release notes.

Riha answered 2/8, 2017 at 15:31 Comment(1)
switch to <hmm> ?? Your before and after are the same.Tequilater
U
1

I gather the documentation wasn't great back in 2012 when this question was asked, but for anyone reading this in 2020+: There's now a whole section in the docs about how to add a source set for integration tests. You really should read it instead of copy/pasting code snippets here and banging your head against the wall trying to figure out why an answer from 2012-2016 doesn't quite work.

The answer is most likely simple but more nuanced than you may think, and the exact code you'll need is likely to be different from the code I'll need. For example, do you want your integration tests to use the same dependencies as your unit tests?

Unhorse answered 28/12, 2020 at 11:19 Comment(0)
D
0

You might also want to configure your tests to run with jUnit:

task integrationTest(type: Test) {
   ...
   useJunitPlatform()
}
Dusk answered 8/12, 2022 at 17:8 Comment(0)
N
0

Here is the solution for Kotlin (build.gradle.kts):

sourceSets {
    create("uiTest") {
        // Adds files from the main source set to the compilation and runtime classpaths of this new source set
        // sourceSets.main.output is a collection of all the directories containing compiled main classes and resources
        compileClasspath += sourceSets.main.get().output
        runtimeClasspath += sourceSets.main.get().output
    }
}

// Makes the uiTestImplementation configuration extend from testImplementaion,
// which means that all the declared dependencies of the test code (and transitively the main as well)
// also become dependencies of this new source set
val uiTestImplementation by configurations.getting {
    extendsFrom(configurations.testImplementation.get())
}
val uiTestRuntimeOnly by configurations.getting {
    extendsFrom(configurations.testRuntimeOnly.get())
}

Bonus:

val uiTest = task<Test>("uiTest") {
    description = "Runs UI tests."
    group = "verification"

    testClassesDirs = sourceSets["uiTest"].output.classesDirs
    classpath = sourceSets["uiTest"].runtimeClasspath

    testLogging {
        events(TestLogEvent.PASSED)
    }
}

tasks.check { dependsOn(uiTest) }
Nonchalance answered 12/9, 2023 at 13:41 Comment(0)
L
-1

I'm new to Gradle, using Gradle 6.0.1 JUnit 4.12. Here's what I came up with to solve this problem.

apply plugin: 'java'
repositories { jcenter() }

dependencies {
    testImplementation 'junit:junit:4.12'
}

sourceSets {
  main {
    java {
       srcDirs = ['src']
    }
  }
  test {
    java {
      srcDirs = ['tests']
    }
  }
}

Notice that the main source and test source is referenced separately, one under main and one under test.

The testImplementation item under dependencies is only used for compiling the source in test. If your main code actually had a dependency on JUnit, then you would also specify implementation under dependencies.

I had to specify the repositories section to get this to work, I doubt that is the best/only way.

Landel answered 23/12, 2019 at 16:46 Comment(2)
idk why this answer is displayed first for me. Sorry, but you haven't added any new sourcesets and thus missed the question.Leathery
But then your answer solved my problem ;-)Fruma

© 2022 - 2024 — McMap. All rights reserved.