Search code examples
kotlingradleintegration-testingsource-setsjvm-test-suite

jvm-test-suite common test sources for use in multiple test suites


Using the jvm-test-suite gradle plugin, I would like to be able to create a common test source set for use in other test suites. I envision the structure to look like the following where the sources and resources from common can be used in unit, integration, functional, and performance:

project/
├─ src/
│  ├─ main/
│  ├─ test/
│  │  ├─ common/
│  │  │  ├─ kotlin/
│  │  │  ├─ resources/
│  │  ├─ unit/
│  │  │  ├─ kotlin/
│  │  │  ├─ resources/
│  │  ├─ integration/
│  │  │  ├─ kotlin/
│  │  │  ├─ resources/
│  │  ├─ functional/
│  │  │  ├─ kotlin/
│  │  │  ├─ resources/
│  │  ├─ performance/
│  │  │  ├─ kotlin/
│  │  │  ├─ resources/

So far I have tried the following, which I thought would provide the proper classpaths for each test suite:

@file:Suppress("UnstableApiUsage")

plugins {
    `jvm-test-suite`
}

// Register `commonTest` source set
sourceSets {
    register("commonTest") {
        java {
            compileClasspath += named("main").get().output
            runtimeClasspath += named("main").get().output
            srcDir("src/test/common/kotlin")
        }
        resources {
            srcDir("src/test/common/resources")
        }
    }
}

// Make `commonTestImplementation` extend from `testImplementation` so that we can use all dependencies that `testImplementation` uses
val commonTestImplementation by configurations.getting {
    extendsFrom(configurations.named("testImplementation").get())
}

configure<TestingExtension> {
    suites {
        val sourceSetMain = sourceSets.named("main").get()
        val sourceSetCommon = sourceSets.named("commonTest").get()

        // These might be able to just be variables instead of lazy evaluation
        val sourceSetMainClasspath = { sourceSetMain.compileClasspath + sourceSetMain.output }
        val sourceSetCommonClasspath = { sourceSetMain.compileClasspath + sourceSetMain.output }

        val test by getting(JvmTestSuite::class) {
            testType.set(TestSuiteType.UNIT_TEST)
            sources {
                // Add common test compile classpath and outputs to the `unitTest` suite?
                compileClasspath += sourceSetCommonClasspath()
                runtimeClasspath += output + compileClasspath
                java {
                    setSrcDirs(listOf("src/test/unit/kotlin"))
                    // I've also tried the following which only works when applied to only 1 test suite but not all. Same with the commented out resources portion directly below
                    // setSrcDirs(listOf("src/test/unit/kotlin", sourceSetCommon.java.srcDirs))
                }
                resources {
                    setSrcDirs(listOf("src/test/unit/resources"))
                    // setSrcDirs(listOf("src/test/unit/resources", sourceSetCommon.resources.srcDirs))
                }
            }
        }

        val functionalTest by registering(JvmTestSuite::class) {
            testType.set(TestSuiteType.FUNCTIONAL_TEST)
            dependencies {
                implementation(project())
            }
            sources {
                // Add common test compile classpath and outputs to the `unitTest` suite?
                compileClasspath += sourceSetCommonClasspath()
                runtimeClasspath += output + compileClasspath
                java {
                    setSrcDirs(listOf("src/test/functional/kotlin"))
                }
                resources {
                    setSrcDirs(listOf("src/test/functional/resources"))
                }
            }

            targets {
                all {
                    testTask.configure {
                        shouldRunAfter(test)
                    }
                }
            }
        }
    }
}

val functionalTestImplementation by configurations.getting {
    extendsFrom(configurations.named("testImplementation").get())
}

From this, I expect to be able to access common test sources in both the unit test (unit) directory and functional test (functional) directory. However, this does not work as expected. Any thoughts/input are greatly appreciated!


Solution

  • I ended up with a much simpler solution which you can see below.

    In this case, I create a new source set called commonTest. Then for each JvmTestSuite type, I add as a dependency: the project and commonTest's output. Since I added the extension methods at the top, this makes configuration for each test set, and any future ones, extremely easy to add.

    plugins {
        `jvm-test-suite`
    }
    
    fun SourceSet.configureSrcSetDirs(dirName: String) {
        java.setSrcDirs(listOf("src/test/$dirName/kotlin"))
        resources.setSrcDirs(listOf("src/test/$dirName/resources"))
    }
    
    fun JvmTestSuite.shouldRunAfter(vararg paths: Any) =
        targets.all {
            testTask.configure {
                shouldRunAfter(paths)
            }
        }
    
    fun SourceSet.addSrcSetMainClasspath() {
        compileClasspath += sourceSets.main.get().compileClasspath + sourceSets.main.get().output
        runtimeClasspath += output + compileClasspath
    }
    
    val commonTest: SourceSet by sourceSets.creating {
        addSrcSetMainClasspath()
        configureSrcSetDirs("common")
    }
    
    testing {
        suites {
            configureEach {
                if (this is JvmTestSuite) {
                    dependencies {
                        implementation(project(path))
                        implementation(commonTest.output)
                    }
                    sources.addSrcSetMainClasspath()
                }
            }
    
            val test by getting(JvmTestSuite::class) {
                testType.set(TestSuiteType.UNIT_TEST)
                sources.configureSrcSetDirs("unit")
            }
    
            val functionalTest by registering(JvmTestSuite::class) {
                testType.set(TestSuiteType.FUNCTIONAL_TEST)
                sources.configureSrcSetDirs("functional")
                shouldRunAfter(test)
            }
    
            val integrationTest by registering(JvmTestSuite::class) {
                testType.set(TestSuiteType.INTEGRATION_TEST)
                sources.configureSrcSetDirs("integration")
                shouldRunAfter(functionalTest)
            }
    
            register<JvmTestSuite>("performanceTest") {
                testType.set(TestSuiteType.PERFORMANCE_TEST)
                sources.configureSrcSetDirs("performance")
                shouldRunAfter(integrationTest)
            }
        }
    }
    
    val testImplementation: Configuration by configurations.getting
    
    val integrationTestImplementation: Configuration by configurations.getting {
        extendsFrom(testImplementation)
    }
    
    val functionalTestImplementation: Configuration by configurations.getting {
        extendsFrom(testImplementation)
    }
    
    val performanceTestImplementation: Configuration by configurations.getting {
        extendsFrom(testImplementation)
    }
    
    tasks.named("check") {
        // already depends on "test"
        dependsOn(testing.suites.named("functionalTest"))
        dependsOn(testing.suites.named("integrationTest"))
        dependsOn(testing.suites.named("performanceTest"))
    }