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.