G'day:
I sat down to continue my "next" blog article, which will be about delegated properties in Kotlin (will cross link here once it's done), and as a first step I ran my tests to remind myself where I got to.
To my dismay I saw a bunch of failing tests. This in itself is OK as I am half-way through doing something, but the tsts that were failing were the ones for a different article: "Kotlin / TDD: writing the tests for a small web service". I slapped my forehead on this because obviously they would fail because I don't have that web service running ATM. And that's legit, so I needed to be able to conditionalise (is that a word? Is now I guess) those tests based on whether the web service is up.
I have previously looked at conditionalising tests in both Kotest ("Kotlin / Gradle / Kotest random exploration"), and then JUnit ("Kotlin: looking at JUnit instead of Kotest"), but in the case of the JUnit ones, they were all "canned" conditions, eg: @EnabledOnOs(OS.LINUX). In this case, I need to base the conditionality on my own logic.
Some quick googling landed me on the ever-helpful Baeldung site, with JUnit 5 Conditional Test Execution with Annotations › 6.2. Conditions. That said, I didn't find that article very helpful, and their examples were writing JS (yes: JS in a Java test; using Nashorn) in a string which seemed like a shit way of doing things to me, and I "knew" there must be a better way. I looked directly at the JUnit5 docs and found what I wanted: JUnit 5 User Guide › 2.8.6. Custom Conditions, their example was this:
@Test
@EnabledIf("customCondition")
void enabled() {
// ...
}
@Test
@DisabledIf("customCondition")
void disabled() {
// ...
}
boolean customCondition() {
return true;
}
I can work with that. I set about writing an isUp function to call on my tests. I did TDD the behaviour of Ktor's HttpClient when getting no response from the server, along these lines:
@Test
fun `it throws an exception if the web service can't be reached`() {
shouldThrow<ConnectException> {
runBlocking {
HttpClient().use { client ->
client.get(webServiceBaseUrl)
}
}
}.message shouldStartWith "Connection refused"
}
But this did not make the cut for my final code as it was a bit tricky to test the function that I use to conditionally enable the tests based on its own response (if you see what I mean: it's a bit self-referential). So only partial TDD on this I guess. Oops.
The final version of the isUp function was this:
fun isUp(): Boolean {
return try {
runBlocking {
HttpClient().use { client ->
client.head(webServiceBaseUrl)
}
}
true
} catch (e: ConnectException) {
false
}
}
I still love how a try/catch is an expression in Kotlin, so I can return it.
I had to horse around a bit when it came to actually using it in the annotation though. Unlike the examples in the docs, I did not want to make just a test method conditional, I wanted the whole class to be conditional, eg:
@DisplayName("Tests of WebService class")
@EnabledIf("isUp")
internal class WebServiceTest {
That's fine. But I had the function inside WebServiceTest, and the annotation could not find it:
I don't really know what "on a null target" means here, but clearly I could not use a object method here. I thought it might be OK if I hauled the function out of the class and had it at the top level, but then it lost access to webServiceBaseUrl and I didn't want to repeat the URL in two places, so… noticed the error said non-static method, and so decided to sling the thing into a companion object (see "Kotlin: there's no such thing as static, apparently") of WebServiceTest:
@EnabledIf("isUp")
internal class WebServiceTest {
private val webserviceUrl = webServiceBaseUrl + "numbers/"
@Serializable
data class SerializableNumber(val id: Int, val en: String, val mi: String)
companion object {
const val webServiceBaseUrl = "http://localhost:8080/"
@JvmStatic
fun isUp(): Boolean {
return try {
runBlocking {
HttpClient().use { client ->
client.head(webServiceBaseUrl)
}
}
true
} catch (e: ConnectException) {
false
}
}
}
// …
}
And that worked fine. Note the @JvmStatic there. Just having the companion object works fine for Kotlin code, but I guess something to do with how the JUnit code is run requires that annotation to make the method seem static from Java's perspective too.
When I have the web service running:
And when it's off:
Compared to before:
Excellent.
Time for a break, and then I'll get back to that delegated properties article. The code for this is on GitHub @ WebServiceTest.kt.
Righto.
--
Adam