21

I'm using this blog post to configure integration tests for a Spring Boot project, but I'm pretty stuck on declaring the source sets. I also found this post on StackOverflow, but I think I'm a bit further already.

My project structure is

project |_ src |_ main | |_ kotlin | |_ resources |_ testIntegration | |_ kotlin | |_ resources |_ test | |_ kotlin | |_ resources |_ build.gradle.kts |_ ... other files 

And build.gradle.kts

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { idea kotlin("jvm") id("org.springframework.boot") version "2.0.5.RELEASE" id("org.jetbrains.kotlin.plugin.spring") version "1.2.71" } fun DependencyHandlerScope.springBoot(module: String) = this.compile("org.springframework.boot:spring-boot-$module:2.0.5.RELEASE") fun DependencyHandlerScope.springBootStarter(module: String) = this.springBoot("starter-$module") dependencies { springBoot("devtools") springBootStarter("batch") springBootStarter("... spring boot dependencies") compile("... more dependencies") testCompile("... more test dependencies") } val test by tasks.getting(Test::class) { useJUnitPlatform { } } kotlin { sourceSets { val integrationTest by creating { kotlin.srcDir("src/testIntegration/kotlin") resources.srcDir("src/testIntegration/resources") } } } val integrationTestCompile by configurations.creating { extendsFrom(configurations["testCompile"]) } val integrationTestRuntime by configurations.creating { extendsFrom(configurations["testRuntime"]) } val testIntegration by tasks.creating(Test::class) { group = "verification" testClassesDirs = kotlin.sourceSets["integrationTest"].kotlin } idea { module { testSourceDirs.addAll(kotlin.sourceSets["integrationTest"].kotlin.srcDirs) testSourceDirs.addAll(kotlin.sourceSets["integrationTest"].resources.srcDirs) } } 

I think I'm pretty much in the right direction. At least it doesn't throw an exception any more :)

When I run the testIntegration task, I get the following output:

Testing started at 12:08 ... 12:08:49: Executing task 'testIntegration'... > Task :project:compileKotlin UP-TO-DATE > Task :project:compileJava NO-SOURCE > Task :project:processResources UP-TO-DATE > Task :project:classes UP-TO-DATE > Task :project:compileTestKotlin UP-TO-DATE > Task :project:compileTestJava NO-SOURCE > Task :project:processTestResources UP-TO-DATE > Task :project:testClasses UP-TO-DATE > Task :project:testIntegration BUILD SUCCESSFUL in 2s 5 actionable tasks: 1 executed, 4 up-to-date 12:08:51: Task execution finished 'testIntegration'. 

Also, IntelliJ doesn't recognise the testIntegration directories as Kotlin packages.

5 Answers 5

27

I was finally able to figure it out thanks to some help on the Kotlin Slack channel. First of all I had to upgrade to Gradle version 4.10.2.

For more info have a look at these two pages from Gradle:

Then I just had to create the sourceSets for the integrationTests

sourceSets { create("integrationTest") { kotlin.srcDir("src/integrationTest/kotlin") resources.srcDir("src/integrationTest/resources") compileClasspath += sourceSets["main"].output + configurations["testRuntimeClasspath"] runtimeClasspath += output + compileClasspath + sourceSets["test"].runtimeClasspath } } 

This would work just fine for Java, but since I'm working with Kotlin I had to add an extra withConvention wrapper

sourceSets { create("integrationTest") { withConvention(KotlinSourceSet::class) { kotlin.srcDir("src/integrationTest/kotlin") resources.srcDir("src/integrationTest/resources") compileClasspath += sourceSets["main"].output + configurations["testRuntimeClasspath"] runtimeClasspath += output + compileClasspath + sourceSets["test"].runtimeClasspath } } } 

In the docs they only put runtimeClasspath += output + compileClasspath, but I added sourceSets["test"].runtimeClasspath so I can directly use the test dependencies instead of declaring new dependencies for the integrationTest task.

Once the sourceSets were created it was a matter of declaring a new task

task<Test>("integrationTest") { description = "Runs the integration tests" group = "verification" testClassesDirs = sourceSets["integrationTest"].output.classesDirs classpath = sourceSets["integrationTest"].runtimeClasspath mustRunAfter(tasks["test"]) } 

After this the tests still didn't run, but that was because I'm using JUnit4. So I just had to add useJUnitPlatform() which makes this the final code

task<Test>("integrationTest") { description = "Runs the integration tests" group = "verification" testClassesDirs = sourceSets["integrationTest"].output.classesDirs classpath = sourceSets["integrationTest"].runtimeClasspath mustRunAfter(tasks["test"]) useJUnitPlatform() } 
Sign up to request clarification or add additional context in comments.

5 Comments

Glad you figured it out, since it helped me with my problem. I find it difficult to understand when to use "withConvention"...it seems always when another language as Java is used (in my case Groovy for Spock tests). It makes sense when you think of the typesystem, but I would find it more consistent if the the withConvention(JavaXX) would be required instead of inferred.
This really helped me, in my case I was trying to configure to run spock and kotlin, so I had to do some minor changes, the part of withConvention is pretty intuitive, at the end I just had to change it to GroovySourceSet: withConvention(org.gradle.api.tasks.GroovySourceSet::class) { groovy.srcDir("src/integration/groovy")
Also to add that if you want to run the test as par of your build process you have to set yoru test task in a variable and add it to your task check: val integrationTest = task<Test>("integrationTest") { .... } tasks.check { dependsOn(integrationTest) }
What's wrong with the org.unbroken-dome.test-sets plugin? You just use it like testSets { create("integrationTest") }. Creating the sourceSets and task yourself means you don't get jacocoReport etc.
withConvention is deprecated in gradle-kotline-dsl-7.5.
6

I didnt like the use of withConvention and how the kotlin src dir was set. So after check out both gradle docs here and here, I came up with this:

sourceSets { create("integrationTest") { kotlin { compileClasspath += main.get().output + configurations.testRuntimeClasspath runtimeClasspath += output + compileClasspath } } } val integrationTest = task<Test>("integrationTest") { description = "Runs the integration tests" group = "verification" testClassesDirs = sourceSets["integrationTest"].output.classesDirs classpath = sourceSets["integrationTest"].runtimeClasspath mustRunAfter(tasks["test"]) } tasks.check { dependsOn(integrationTest) } 

I preferr the less verbose style when using kotlin { and the use of variable for the new integrationTestTask.

1 Comment

The kotlin call inside the create function call is from the project, not from the sourceset created. You can remove it and it does the same
4

As of Gradle 5.2.1 see https://docs.gradle.org/current/userguide/java_testing.html#sec:configuring_java_integration_tests

sourceSets { create("intTest") { compileClasspath += sourceSets.main.get().output runtimeClasspath += sourceSets.main.get().output } } val intTestImplementation by configurations.getting { extendsFrom(configurations.testImplementation.get()) } configurations["intTestRuntimeOnly"].extendsFrom(configurations.runtimeOnly.get()) dependencies { intTestImplementation("junit:junit:4.12") } val integrationTest = task<Test>("integrationTest") { description = "Runs integration tests." group = "verification" testClassesDirs = sourceSets["intTest"].output.classesDirs classpath = sourceSets["intTest"].runtimeClasspath shouldRunAfter("test") } tasks.check { dependsOn(integrationTest) } 

1 Comment

what you only did was to copy the piece of code from Gradle's documentation: docs.gradle.org/current/userguide/…
2

There is a dedicated Gradle feature called Declarative Test Suite that supports this case:

testing { suites { val test by getting(JvmTestSuite::class) { useJUnitJupiter() } register("integrationTest", JvmTestSuite::class) { dependencies { implementation(project()) } targets { all { testTask.configure { shouldRunAfter(test) } } } } } } 

More: https://docs.gradle.org/current/userguide/java_testing.html#sec:configuring_java_integration_tests

Comments

1

Here is git repo that you can refer to: enter link description here

import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.gradle.api.tasks.testing.logging.TestLogEvent plugins { application kotlin("jvm") version "1.3.72" id("com.diffplug.gradle.spotless") version "3.24.2" id("org.jmailen.kotlinter") version "1.26.0" checkstyle } version = "1.0.2" group = "org.sample" application { mainClass.set("org.sample.MainKt") } repositories { mavenCentral() jcenter() } tasks.checkstyleMain { group = "verification" } tasks.checkstyleTest { group = "verification" } spotless { kotlin { ktlint() } kotlinGradle { target(fileTree(projectDir).apply { include("*.gradle.kts") } + fileTree("src").apply { include("**/*.gradle.kts") }) ktlint() } } tasks.withType<Test> { useJUnitPlatform() testLogging { lifecycle { events = mutableSetOf(TestLogEvent.FAILED, TestLogEvent.PASSED, TestLogEvent.SKIPPED) exceptionFormat = TestExceptionFormat.FULL showExceptions = true showCauses = true showStackTraces = true showStandardStreams = true } info.events = lifecycle.events info.exceptionFormat = lifecycle.exceptionFormat } val failedTests = mutableListOf<TestDescriptor>() val skippedTests = mutableListOf<TestDescriptor>() addTestListener(object : TestListener { override fun beforeSuite(suite: TestDescriptor) {} override fun beforeTest(testDescriptor: TestDescriptor) {} override fun afterTest(testDescriptor: TestDescriptor, result: TestResult) { when (result.resultType) { TestResult.ResultType.FAILURE -> failedTests.add(testDescriptor) TestResult.ResultType.SKIPPED -> skippedTests.add(testDescriptor) else -> Unit } } override fun afterSuite(suite: TestDescriptor, result: TestResult) { if (suite.parent == null) { // root suite logger.lifecycle("----") logger.lifecycle("Test result: ${result.resultType}") logger.lifecycle( "Test summary: ${result.testCount} tests, " + "${result.successfulTestCount} succeeded, " + "${result.failedTestCount} failed, " + "${result.skippedTestCount} skipped") failedTests.takeIf { it.isNotEmpty() }?.prefixedSummary("\tFailed Tests") skippedTests.takeIf { it.isNotEmpty() }?.prefixedSummary("\tSkipped Tests:") } } private infix fun List<TestDescriptor>.prefixedSummary(subject: String) { logger.lifecycle(subject) forEach { test -> logger.lifecycle("\t\t${test.displayName()}") } } private fun TestDescriptor.displayName() = parent?.let { "${it.name} - $name" } ?: "$name" }) } dependencies { implementation(kotlin("stdlib")) implementation("com.sparkjava:spark-core:2.5.4") implementation("org.slf4j:slf4j-simple:1.7.30") testImplementation("com.squareup.okhttp:okhttp:2.5.0") testImplementation("io.kotest:kotest-runner-junit5-jvm:4.0.5") testImplementation("io.kotest:kotest-assertions-core-jvm:4.0.5") // for kotest core jvm assertions testImplementation("io.kotest:kotest-property-jvm:4.0.5") } sourceSets { create("integTest") { kotlin { compileClasspath += main.get().output + configurations.testRuntimeClasspath runtimeClasspath += output + compileClasspath } } } val integTest = task<Test>("integTest") { description = "Runs the integTest tests" group = "verification" testClassesDirs = sourceSets["integTest"].output.classesDirs classpath = sourceSets["integTest"].runtimeClasspath mustRunAfter(tasks["test"]) } tasks.check { dependsOn(integTest) } sourceSets { create("journeyTest") { kotlin { compileClasspath += main.get().output + configurations.testRuntimeClasspath runtimeClasspath += output + compileClasspath } } } val journeyTest = task<Test>("journeyTest") { description = "Runs the JourneyTest tests" group = "verification" testClassesDirs = sourceSets["journeyTest"].output.classesDirs classpath = sourceSets["journeyTest"].runtimeClasspath mustRunAfter(tasks["integTest"]) } tasks.check { dependsOn(journeyTest) } 

I hope this helps. :)

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.