Kotlin, Spring, Kafka, JUnit 5

This post will offer some useful and specific information on JUnit 5, RegisterExtensions, and Kotlin. If you’re here just for those skip forward to the Solution section.

Satisfying Knowledge

I enjoy woodworking. An aspect about it I like is that mostly when you learn things, you understand why they are true. As an example, for light grainy woods, to avoid splitting, it helps to slightly blunt the tips of nails before driving them in. You want the nail to actually crush the wood a bit at first, and not immediately act like a wedge and cleave the woods grain. It make the technique easy to remember, obvious when to apply, and was satisfying to learn.

Unsatisfying Knowledge

I’ve been working with Kafka in a Kotlin service built in Spring. I’d tested the Kafka related code employing an embedded Kafka. Spring has some automagic support for that but it hadn’t fit our needs out of the box so we’d added some set up and tear down. I was happy with the coverage, but I’d duplicated the set up and tear down code a number of times in different tests which I wanted to fix. A JUnit 5 RegisterExtension fit the task and so I moved the code into one, employing that I could remove the duplicated code. Success. Well sort of … the tests still ran and passed, however the build itself failed (!?). The final shut down of the JVM was now throwing an exception. Using an extension had apparently changed some internal behavior. A daemon thread internal to Kafka was trying to access a log folder in a temporary area, but the temporary area had been previously removed. I could move the log area aside and avoid the exception, but then things just hung after the final test. I doggedly worked at it until I solved it, but the knowledge of the solution is unsatisfying, because I can say with certainty what works but I can’t tell you why.

The Solution

JUnit 5 provides nice extension mechanisms for creating reusable set up and tear down code that can be applied to collections of tests. The examples usually read about as follows:

public class TestWithExtensionTest {
    @RegisterExtension
    static SomeExtension ext = new SomeExtension();
 }

Now, Kotlin shed the static feature, but there is a way to achieve them under the cover, and so you’ll see Kotlin examples parallel the above as follow:

class TestWithExtensionTest {
   companion object {
      @JvmField
      @RegisterExtension
      val ext: SomeExtension = SomeExtension()
   }
}

And that works. Mostly. In my case it only sort of worked. The extension ran and my tests worked, but the JVM running the tests exited with an exception. I spent wasted time trying to address the specific exception. In the end, the extension worked perfectly, unchanged, by simply using it as follows:

@ExtendWith(SomeExtension::class)
class TestWithExtensionTest {
}

By applying the extension with the annotation instead, the problem went away. It’s a cleaner approach too.

Why changing how I registered the extension solved the problem… I can’t say. Not satisfying.

Gradle, JUnit5, Jacoco Working

The way information is dispersed on the Internet is pretty cool sometimes.  I’ve visited this same topic a couple times and never managed to get JUnit5 and Jacoco working with Gradle.  Fresh searches never turned up a working solution.  The other day, looking at my blog stats I saw an intriguing referrer to one of my past posts. The reference was from a blog written in Japanese. Google translate yielded an awkward version that seemed to offer a solution.  Since the Gradle code needed no translation I tried out some of the changes shown and – viola!

So, Here’s the Working Solution

The goal was to employ JUnit5, generate Jacoco reports, and feed those into Codecov.com. Here’s the snippet of working solution.

buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'org.junit.platform:junit-platform-gradle-plugin:1.1.0-RC1'
}
}
apply plugin: "jacoco"
apply plugin: 'org.junit.platform.gradle.plugin'
repositories {
jcenter()
}
dependencies {
compile 'org.junit.jupiter:junit-jupiter-api:5.1.0-RC1'
testRuntime 'org.junit.jupiter:junit-jupiter-engine:5.1.0-RC1'
}
junitPlatform.enableStandardTestTask true
jacoco {
toolVersion = '0.7.9'
applyTo junitPlatformTest
}
jacocoTestReport {
reports {
xml.enabled = true
html.enabled = true
}
}
view raw build.gradle hosted with ❤ by GitHub

I believe the secret sauce I missed was in lines 23 and 27.  To see the project that I tested this with, the repo is here.