Kotlin - Add Integration Test Module
25 Jul 2018The default package structure for a new Kotlin project generated through IntelliJ looks like the following, whereby you have a main source folder with source sets
(modules) for your main
files and then test
source files.
Typically, you would place your unit tests within the auto-generated test
module, and then run them all at once (within one JVM). IntelliJ is generally set up to support this use case and if that’s all you need, requires minimal setup and effort.
However, if you also need to add integration tests (or end-to-end etc), then this project structure can start to cause issues. For example, consider a typical project setup for a server-side app:
main
- business logic and main app filestest
- unit tests,test-integration
- integration/e2e tests- typically testing API endpoints with Rest Assured or similar
- start up full version of the server and any dependencies
You can’t merge all the tests into one module and run them all at once because you would need to start up multiple database instances etc. Conflicts arise and it’s apparent that you need to run them separately in their own dedicated JVM.
To add the above mentioned testIntegration
module, you can make some edits to your build.gradle
file to define a new source set
(IntelliJ module):
sourceSets {
testIntegration {
java.srcDir 'src/testIntegration/java'
kotlin.srcDir 'src/testIntegration/kotlin'
resources.srcDir 'src/testIntegration/resources'
compileClasspath += main.output
runtimeClasspath += main.output
}
}
Here, a new source set
for integration tests is created. Gradle is told where the Java and Kotlin source files live and we specify that the classpath inherits from the main
source set. This allows you to reference classes of your main
module within the integration tests (you might not need this).
Then, we provide a configuration
and task
for the new source set to ensure that the new module contains the same dependencies as within the main test
module (defined using testCompile
in your dependencies
). Finally, define a new Task
to run the integration tests, pointing it to the classes and classpath of the testIntegration
source set instead of the inherited defaults from test
:
configurations {
testIntegrationImplementation.extendsFrom testImplementation
testIntegrationRuntime.extendsFrom testRuntime
}
task testIntegration(type: Test) {
testClassesDirs = sourceSets.testIntegration.output.classesDirs
classpath = sourceSets.testIntegration.runtimeClasspath
}
Similarly to how you might have previously set the target bytecode version for the main
and test
modules, you need to do the same for the new module:
compileTestIntegrationKotlin {
kotlinOptions.jvmTarget = "1.8"
}
If you run Gradle with the option to ‘Create directories for empty content roots automatically’, you should see a new module get created. You might notice one issue though, the new module is not marked as a test module within IntelliJ. You could do this manually, but it would get reset every time Gradle runs. To override this, you can apply the idea
plugin and add the source directories of the new source set:
idea {
module {
testSourceDirs += project.sourceSets.testIntegration.java.srcDirs
testSourceDirs += project.sourceSets.testIntegration.kotlin.srcDirs
testSourceDirs += project.sourceSets.testIntegration.resources.srcDirs
}
}
Now you will see the desired output after Gradle runs:
WARNING - This approach is not without problems. If you look at the Test Output Path
of the new module, it is defined as \kotlin-scratchpad\out\test\classes
which is the same as the main test
module. Therefore, all the compiled test classes will end up in the same directory - which causes issues if you try to Run All
for example. To fix this, you have to manually update the path to \kotlin-scratchpad\out\testIntegration\classes
. Alternatively, you might not apply the idea
plugin and just mark the module for tests each time Gradle runs. Hopefully I will find a fix for this at some point.