Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Integration tests with Gradle Kotlin DSL

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.

like image 782
Johan Vergeer Avatar asked Oct 20 '18 10:10

Johan Vergeer


People also ask

Is Gradle a DSL?

gradle file which helps Gradle build tool to specify this build is a debug build with certain keyAlias etc. So these building blocks, API, etc are formats to use Gradle in android, hence called Gradle DSL (domain-specific language).

What is Kotlin Gradle DSL?

Gradle's Kotlin DSL provides an alternative syntax to the traditional Groovy DSL with an enhanced editing experience in supported IDEs, with superior content assist, refactoring, documentation, and more.

How do I run an integration test in intelliJ Gradle?

To run integration tests from intelliJ, locate the /src/integration-test package as shown below. Right-click on the entire integration test suite, or choose a targeted integration test, then choose the Run '{chosen test}' option. After running an integration test, the intelliJ event log will display test result data.


2 Answers

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:

  • https://docs.gradle.org/release-nightly/userguide/organizing_gradle_projects.html#sec:separate_test_type_source_files
  • https://docs.gradle.org/release-nightly/userguide/organizing_gradle_projects.html#sec:separate_test_type_source_files

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()
}
like image 163
Johan Vergeer Avatar answered Nov 07 '22 10:11

Johan Vergeer


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.

like image 27
Joker Avatar answered Nov 07 '22 10:11

Joker