Parameterized Testing With JUnit 5 For Kotlin

I am a fan of parameterized testing as it is found in JUnit 5, but when you start a new Kotlin JVM project in IntelliJ with Gradle it defaults to JUnit 4. I’ve found several tutorials and a great deal of documentation about getting Kotlin and JUnit 5 to work together, but none of them worked for me completely. Arho Huttunen’s blog post was especially helpful until I got to runtime. This is what I finally got to work.

These are the languages and software packages I used:

  • JUnit 5.7.0
  • Kotlin 1.4
  • Gradle 6.3
  • Intellij’s IDEA 2020.2
  • JVM 11 (but should work with 1.8+)

There are multiple parts to this. Through it all IDEA flagged sections of it red with errors, and these did not go away (even though it could compile and run) until I restarted the IDE.

First you need to tell build.gradle to import the correct version of JUnit.

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.7.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
}

tasks.test {
    useJUnitPlatform()
}

This allows IDEA to parse the imports in your test file, despite throwing errors about not understanding the imports.

Since this was a proof of concept, I decided to build parameterized testing for a simple calculator app. I tried using @CsvSource with the @ParameterizedTest annotation, but due to the static issue, it wouldn’t compile, and I had to switch to @MethodSource to pass the data.

The static issue is that the parameters for a parameterized test need to come from a static method, but Kotlin does not have the concept of static. Fortunately it has the @JvmStatic annotation. The entire thing needs to be in a companion object.

companion object MultiplyParameters {
    @JvmStatic
    fun multiplyArguments(): Stream<Arguments> =
        Stream.of(
            Arguments.of(2, 2, 4),
            Arguments.of(3, 3, 9),
            Arguments.of(2, 3, 6)
        )
}

@ParameterizedTest
@MethodSource("multiplyArguments")
fun multiplyTest(int1: Int, int2: Int, expected: Int) {
    assertEquals(expected, calc.multiply(int1, int2))
}

Because there can only be one companion object per class you’ll need a separate function within that companion object for each set of parameters you’d like to use. (An example using the full text of the file can be found at the end of this post.)

And this is where following the blog post quit working for me. Every time I attempted to run the program I received the error message Calls to static methods in Java interfaces are prohibited in JVM target 1.6. Recompile with '-jvm-target 1.8' For some unknown reason, IDEA likes to run the tests using JVM 1.6. (I’m not sure I even have that version installed on my computer.) The answer to this was to tell Gradle that it needs to use a more recent JVM to compile the tests. I chose to use Java 11 as that’s what I’m using for the project, but you should be able to do it with any version from 1.8 on. Once I added the magic line to build.gradle, I was able to run the tests despite the sea of red error messages I was getting in IDEA. (Again, restarting the IDE fixed the error messages.)

compileTestKotlin.kotlinOptions.jvmTarget 
    = JavaVersion.VERSION_11.toString()

In the end my build.gradle file looked like this:

plugins {
    id 'java'
    id 'org.jetbrains.kotlin.jvm' version '1.4.21'
}

group 'com.laborofloathing'
version '1.0-SNAPSHOT'

compileTestKotlin.kotlinOptions.jvmTarget 
    = JavaVersion.VERSION_11.toString()

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib"

    testImplementation 'org.junit.jupiter:junit-jupiter:5.7.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
}

tasks.test {
    useJUnitPlatform()
}

And this is what the testing file looked like when I added a second test:

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import java.util.stream.Stream

class CalculatorTest {

    private val calc = Calculator()

    companion object MultiplyParameters {
        @JvmStatic
        fun multiplyArguments(): Stream<Arguments> =
            Stream.of(
                Arguments.of(2, 2, 4),
                Arguments.of(3, 3, 9),
                Arguments.of(2, 3, 6)
            )

        @JvmStatic
        fun additionArguments(): Stream<Arguments> =
            Stream.of(
                Arguments.of(2, 2, 4),
                Arguments.of(3, 3, 6),
                Arguments.of(2, 3, 5)
            )
    }

    @ParameterizedTest
    @MethodSource("multiplyArguments")
    fun multiplyTest(int1: Int, int2: Int, expected: Int) {
        assertEquals(expected, calc.multiply(int1, int2))
    }

    @ParameterizedTest
    @MethodSource("additionArguments")
    fun additionTest(int1: Int, int2: Int, expected: Int) {
        assertEquals(expected, calc.add(int1, int2))
    }
}

Leave a comment

Your email address will not be published.